home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 1843 / 1843.xpi / content / firebug / net.js < prev    next >
Text File  |  2010-01-15  |  165KB  |  5,209 lines

  1. /* See license.txt for terms of usage */
  2.  
  3. FBL.ns(function() { with (FBL) {
  4.  
  5. // ************************************************************************************************
  6. // Constants
  7.  
  8. const Cc = Components.classes;
  9. const Ci = Components.interfaces;
  10. const Cr = Components.results;
  11.  
  12. const CacheService = Cc["@mozilla.org/network/cache-service;1"];
  13. const ImgCache = Cc["@mozilla.org/image/cache;1"];
  14. const IOService = Cc["@mozilla.org/network/io-service;1"];
  15. const prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch2);
  16.  
  17. const NOTIFY_ALL = Ci.nsIWebProgress.NOTIFY_ALL;
  18.  
  19. const nsIWebProgressListener = Ci.nsIWebProgressListener;
  20. const STATE_IS_WINDOW = nsIWebProgressListener.STATE_IS_WINDOW;
  21. const STATE_IS_DOCUMENT = nsIWebProgressListener.STATE_IS_DOCUMENT;
  22. const STATE_IS_NETWORK = nsIWebProgressListener.STATE_IS_NETWORK;
  23. const STATE_IS_REQUEST = nsIWebProgressListener.STATE_IS_REQUEST;
  24. const STATE_START = nsIWebProgressListener.STATE_START;
  25. const STATE_STOP = nsIWebProgressListener.STATE_STOP;
  26. const STATE_TRANSFERRING = nsIWebProgressListener.STATE_TRANSFERRING;
  27.  
  28. const LOAD_BACKGROUND = Ci.nsIRequest.LOAD_BACKGROUND;
  29. const LOAD_FROM_CACHE = Ci.nsIRequest.LOAD_FROM_CACHE;
  30. const LOAD_DOCUMENT_URI = Ci.nsIChannel.LOAD_DOCUMENT_URI;
  31.  
  32. const NS_ERROR_CACHE_KEY_NOT_FOUND = 0x804B003D;
  33. const NS_ERROR_CACHE_WAIT_FOR_VALIDATION = 0x804B0040;
  34.  
  35. var nsIHttpActivityObserver = Ci.nsIHttpActivityObserver;
  36. var nsIHttpActivityObserver = Ci.nsIHttpActivityObserver;
  37. var nsISocketTransport = Ci.nsISocketTransport;
  38.  
  39. // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  40.  
  41. const reIgnore = /about:|javascript:|resource:|chrome:|jar:/;
  42. const layoutInterval = 300;
  43. const indentWidth = 18;
  44.  
  45. var cacheSession = null;
  46. var contexts = new Array();
  47. var panelName = "net";
  48. var maxQueueRequests = 500;
  49. var panelBar1 = $("fbPanelBar1");
  50. var activeRequests = [];
  51.  
  52. // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  53.  
  54. const mimeExtensionMap =
  55. {
  56.     "txt": "text/plain",
  57.     "html": "text/html",
  58.     "htm": "text/html",
  59.     "xhtml": "text/html",
  60.     "xml": "text/xml",
  61.     "css": "text/css",
  62.     "js": "application/x-javascript",
  63.     "jss": "application/x-javascript",
  64.     "jpg": "image/jpg",
  65.     "jpeg": "image/jpeg",
  66.     "gif": "image/gif",
  67.     "png": "image/png",
  68.     "bmp": "image/bmp",
  69.     "swf": "application/x-shockwave-flash",
  70.     "flv": "video/x-flv"
  71. };
  72.  
  73. const fileCategories =
  74. {
  75.     "undefined": 1,
  76.     "html": 1,
  77.     "css": 1,
  78.     "js": 1,
  79.     "xhr": 1,
  80.     "image": 1,
  81.     "flash": 1,
  82.     "txt": 1,
  83.     "bin": 1
  84. };
  85.  
  86. const textFileCategories =
  87. {
  88.     "txt": 1,
  89.     "html": 1,
  90.     "xhr": 1,
  91.     "css": 1,
  92.     "js": 1
  93. };
  94.  
  95. const binaryFileCategories =
  96. {
  97.     "bin": 1,
  98.     "flash": 1
  99. };
  100.  
  101. const mimeCategoryMap =
  102. {
  103.     "text/plain": "txt",
  104.     "application/octet-stream": "bin",
  105.     "text/html": "html",
  106.     "text/xml": "html",
  107.     "text/css": "css",
  108.     "application/x-javascript": "js",
  109.     "text/javascript": "js",
  110.     "application/javascript" : "js",
  111.     "image/jpeg": "image",
  112.     "image/jpg": "image",
  113.     "image/gif": "image",
  114.     "image/png": "image",
  115.     "image/bmp": "image",
  116.     "application/x-shockwave-flash": "flash",
  117.     "video/x-flv": "flash"
  118. };
  119.  
  120. const binaryCategoryMap =
  121. {
  122.     "image": 1,
  123.     "flash" : 1
  124. };
  125.  
  126. // ************************************************************************************************
  127.  
  128. /**
  129.  * @module Represents a module object for the Net panel. This object is derived
  130.  * from <code>Firebug.ActivableModule</code> in order to support activation (enable/disable).
  131.  * This allows to avoid (performance) expensive features if the functionality is not necessary
  132.  * for the user.
  133.  */
  134. Firebug.NetMonitor = extend(Firebug.ActivableModule,
  135. {
  136.     dispatchName: "netMonitor",
  137.     clear: function(context)
  138.     {
  139.         // The user pressed a Clear button so, remove content of the panel...
  140.         var panel = context.getPanel(panelName, true);
  141.         if (panel)
  142.             panel.clear();
  143.     },
  144.  
  145.     onToggleFilter: function(context, filterCategory)
  146.     {
  147.         if (!context.netProgress)
  148.             return;
  149.  
  150.         Firebug.setPref(Firebug.prefDomain, "netFilterCategory", filterCategory);
  151.  
  152.         // The content filter has been changed. Make sure that the content
  153.         // of the panel is updated (CSS is used to hide or show individual files).
  154.         var panel = context.getPanel(panelName, true);
  155.         if (panel)
  156.         {
  157.             panel.setFilter(filterCategory);
  158.             panel.updateSummaries(now(), true);
  159.         }
  160.     },
  161.  
  162.     syncFilterButtons: function(chrome)
  163.     {
  164.         var button = chrome.$("fbNetFilter-" + Firebug.netFilterCategory);
  165.         button.checked = true;
  166.     },
  167.  
  168.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  169.     // extends Module
  170.  
  171.     initializeUI: function()
  172.     {
  173.         Firebug.ActivableModule.initializeUI.apply(this, arguments);
  174.  
  175.         // Initialize max limit for logged requests.
  176.         NetLimit.updateMaxLimit();
  177.  
  178.         // Synchronize UI buttons with the current filter.
  179.         this.syncFilterButtons(FirebugChrome);
  180.  
  181.         prefs.addObserver(Firebug.prefDomain, NetLimit, false);
  182.     },
  183.  
  184.     initialize: function()
  185.     {
  186.         this.panelName = panelName;
  187.  
  188.         Firebug.ActivableModule.initialize.apply(this, arguments);
  189.  
  190.         if (Firebug.TraceModule)
  191.             Firebug.TraceModule.addListener(this.TraceListener);
  192.  
  193.         // HTTP observer must be registered now (and not in monitorContext, since if a
  194.         // page is opened in a new tab the top document request would be missed otherwise.
  195.         NetHttpObserver.registerObserver();
  196.         NetHttpActivityObserver.registerObserver();
  197.  
  198.         Firebug.Debugger.addListener(this.DebuggerListener);
  199.     },
  200.  
  201.     internationalizeUI: function(doc)
  202.     {
  203.         var element = doc.getElementById("fbNetPersist");
  204.         FBL.internationalize(element, "label");
  205.         FBL.internationalize(element, "tooltiptext");
  206.     },
  207.  
  208.     shutdown: function()
  209.     {
  210.         prefs.removeObserver(Firebug.prefDomain, this, false);
  211.         if (Firebug.TraceModule)
  212.             Firebug.TraceModule.removeListener(this.TraceListener);
  213.  
  214.         NetHttpObserver.unregisterObserver();
  215.         NetHttpActivityObserver.unregisterObserver();
  216.  
  217.         Firebug.Debugger.removeListener(this.DebuggerListener);
  218.     },
  219.  
  220.     initContext: function(context, persistedState)
  221.     {
  222.         Firebug.ActivableModule.initContext.apply(this, arguments);
  223.  
  224.         if (context.window && 'addEventListener' in context.window)
  225.         {
  226.             var window = context.window;
  227.  
  228.             // Register "load" listener in order to track window load time.
  229.             var onWindowLoadHandler = function() {
  230.                 if (context.netProgress)
  231.                     context.netProgress.post(windowLoad, [window, now()]);
  232.                 window.removeEventListener("load", onWindowLoadHandler, true);
  233.             }
  234.             window.addEventListener("load", onWindowLoadHandler, true);
  235.  
  236.             // Register "DOMContentLoaded" listener to track timing.
  237.             var onContentLoadHandler = function() {
  238.                 if (context.netProgress)
  239.                     context.netProgress.post(contentLoad, [window, now()]);
  240.                 window.removeEventListener("DOMContentLoaded", onContentLoadHandler, true);
  241.             }
  242.  
  243.             window.addEventListener("DOMContentLoaded", onContentLoadHandler, true);
  244.         }
  245.  
  246.         if (Firebug.NetMonitor.isAlwaysEnabled())
  247.             monitorContext(context);
  248.  
  249.         if (context.netProgress)
  250.         {
  251.             // Load existing breakpoints
  252.             var persistedPanelState = getPersistedState(context, panelName);
  253.             if (persistedPanelState.breakpoints)
  254.                 context.netProgress.breakpoints = persistedPanelState.breakpoints;
  255.         }
  256.     },
  257.  
  258.     reattachContext: function(browser, context)
  259.     {
  260.         Firebug.ActivableModule.reattachContext.apply(this, arguments);
  261.         this.syncFilterButtons(Firebug.chrome);
  262.     },
  263.  
  264.     destroyContext: function(context, persistedState)
  265.     {
  266.         Firebug.ActivableModule.destroyContext.apply(this, arguments);
  267.  
  268.         if (context.netProgress)
  269.         {
  270.             // Remember existing breakpoints.
  271.             var persistedPanelState = getPersistedState(context, panelName);
  272.             persistedPanelState.breakpoints = context.netProgress.breakpoints;
  273.         }
  274.  
  275.         if (Firebug.NetMonitor.isAlwaysEnabled())
  276.             unmonitorContext(context);
  277.     },
  278.  
  279.     showContext: function(browser, context)
  280.     {
  281.         Firebug.ActivableModule.showContext.apply(this, arguments);
  282.  
  283.     },
  284.  
  285.     loadedContext: function(context)
  286.     {
  287.         var tabId = Firebug.getTabIdForWindow(context.browser.contentWindow);
  288.         delete contexts[tabId];
  289.  
  290.         var netProgress = context.netProgress;
  291.         if (netProgress)
  292.         {
  293.             netProgress.loaded = true;
  294.  
  295.             // Set Page title and id into all document objects.
  296.             for (var i=0; i<netProgress.documents.length; i++)
  297.             {
  298.                 var doc = netProgress.documents[i];
  299.                 doc.id = context.uid;
  300.                 doc.title = context.getTitle();
  301.             }
  302.         }
  303.     },
  304.  
  305.     onEnabled: function(context)
  306.     {
  307.         NetHttpActivityObserver.registerObserver();
  308.  
  309.         monitorContext(context);
  310.     },
  311.  
  312.     onDisabled: function(context)
  313.     {
  314.         NetHttpActivityObserver.unregisterObserver();
  315.  
  316.         unmonitorContext(context);
  317.     },
  318.  
  319.     onResumeFirebug: function()
  320.     {
  321.         if (Firebug.NetMonitor.isAlwaysEnabled())
  322.             TabWatcher.iterateContexts(monitorContext);
  323.     },
  324.  
  325.     onSuspendFirebug: function()
  326.     {
  327.         if (Firebug.NetMonitor.isAlwaysEnabled())
  328.             TabWatcher.iterateContexts(unmonitorContext);
  329.     },
  330.  
  331.     togglePersist: function(context)
  332.     {
  333.         var panel = context.getPanel(panelName);
  334.         panel.persistContent = panel.persistContent ? false : true;
  335.         Firebug.chrome.setGlobalAttribute("cmd_togglePersistNet", "checked", panel.persistContent);
  336.     }
  337. });
  338.  
  339. // ************************************************************************************************
  340.  
  341. /**
  342.  * @panel Represents a Firebug panel that displayes info about HTTP activity associated with
  343.  * the current page. This class is derived from <code>Firebug.ActivablePanel</code> in order
  344.  * to support activation (enable/disable). This allows to avoid (performance) expensive
  345.  * features if the functionality is not necessary for the user.
  346.  */
  347. function NetPanel() {}
  348. NetPanel.prototype = extend(Firebug.ActivablePanel,
  349. {
  350.     name: panelName,
  351.     searchable: true,
  352.     editable: true,
  353.     breakable: true,
  354.  
  355.     initialize: function(context, doc)
  356.     {
  357.         this.queue = [];
  358.  
  359.         Firebug.registerUIListener(this);
  360.  
  361.         this.onContextMenu = bind(this.onContextMenu, this);
  362.  
  363.         Firebug.ActivablePanel.initialize.apply(this, arguments);
  364.     },
  365.  
  366.     destroy: function(state)
  367.     {
  368.         Firebug.ActivablePanel.destroy.apply(this, arguments);
  369.  
  370.         Firebug.unregisterUIListener(this);
  371.     },
  372.  
  373.     initializeNode : function()
  374.     {
  375.         this.panelNode.addEventListener("contextmenu", this.onContextMenu, false);
  376.  
  377.         dispatch([Firebug.A11yModel], "onInitializeNode", [this]);
  378.     },
  379.  
  380.     destroyNode : function()
  381.     {
  382.         this.panelNode.removeEventListener("contextmenu", this.onContextMenu, false);
  383.  
  384.         dispatch([Firebug.A11yModel], "onDestroyNode", [this]);
  385.     },
  386.  
  387.     loadPersistedContent: function(state)
  388.     {
  389.         this.initLayout();
  390.  
  391.         var tbody = this.table.firstChild;
  392.         var lastRow = tbody.lastChild.previousSibling;
  393.  
  394.         // Move all net-rows from the persistedState to this panel.
  395.         var prevTableBody = state.panelNode.getElementsByClassName("netTableBody").item(0);
  396.         if (!prevTableBody)
  397.             return;
  398.  
  399.         var files = [];
  400.  
  401.         while (prevTableBody.firstChild)
  402.         {
  403.             var row = prevTableBody.firstChild;
  404.             if (hasClass(row, "netRow") && hasClass(row, "hasHeaders") && !hasClass(row, "history"))
  405.             {
  406.                 row.repObject.history = true;
  407.                 files.push({
  408.                     file: row.repObject,
  409.                     offset: 0 + "%",
  410.                     width: 0 + "%",
  411.                     elapsed:  -1
  412.                 });
  413.             }
  414.  
  415.             if (hasClass(row, "netPageRow"))
  416.             {
  417.                 removeClass(row, "opened");
  418.                 tbody.insertBefore(row, lastRow);
  419.             }
  420.             else
  421.                 prevTableBody.removeChild(row);
  422.         }
  423.  
  424.         if (files.length)
  425.         {
  426.             var pageRow = NetPage.pageTag.insertRows({page: state}, lastRow)[0];
  427.             pageRow.files = files;
  428.  
  429.             lastRow = tbody.lastChild.previousSibling;
  430.         }
  431.  
  432.         if (this.table.getElementsByClassName("netPageRow").item(0))
  433.             NetPage.separatorTag.insertRows({}, lastRow);
  434.  
  435.         scrollToBottom(this.panelNode);
  436.     },
  437.  
  438.     savePersistedContent: function(state)
  439.     {
  440.         Firebug.ActivablePanel.savePersistedContent.apply(this, arguments);
  441.  
  442.         state.pageTitle = this.context.getTitle();
  443.     },
  444.  
  445.     // UI Listener
  446.     showUI: function(browser, context)
  447.     {
  448.     },
  449.  
  450.     hideUI: function(browser, context)
  451.     {
  452.     },
  453.  
  454.     show: function(state)
  455.     {
  456.         var enabled = Firebug.NetMonitor.isAlwaysEnabled();
  457.         this.showToolbarButtons("fbNetButtons", enabled);
  458.  
  459.         if (enabled)
  460.         {
  461.             Firebug.NetMonitor.disabledPanelPage.hide(this);
  462.             Firebug.chrome.setGlobalAttribute("cmd_togglePersistNet", "checked", this.persistContent);
  463.         }
  464.         else
  465.         {
  466.             Firebug.NetMonitor.disabledPanelPage.show(this);
  467.             this.table = null;
  468.         }
  469.  
  470.         if (!enabled)
  471.             return;
  472.  
  473.         if (!this.filterCategory)
  474.             this.setFilter(Firebug.netFilterCategory);
  475.  
  476.         this.layout();
  477.  
  478.         if (!this.layoutInterval)
  479.             this.layoutInterval = setInterval(bindFixed(this.updateLayout, this), layoutInterval);
  480.  
  481.         if (this.wasScrolledToBottom)
  482.             scrollToBottom(this.panelNode);
  483.     },
  484.  
  485.     hide: function()
  486.     {
  487.         this.showToolbarButtons("fbNetButtons", false);
  488.  
  489.         Firebug.Debugger.syncCommands(this.context);
  490.  
  491.         delete this.infoTipURL;  // clear the state that is tracking the infotip so it is reset after next show()
  492.         this.wasScrolledToBottom = isScrolledToBottom(this.panelNode);
  493.  
  494.         clearInterval(this.layoutInterval);
  495.         delete this.layoutInterval;
  496.     },
  497.  
  498.     updateOption: function(name, value)
  499.     {
  500.         if (name == "netFilterCategory")
  501.         {
  502.             Firebug.NetMonitor.syncFilterButtons(Firebug.chrome);
  503.             for (var i = 0; i < TabWatcher.contexts.length; ++i)
  504.             {
  505.                 var context = TabWatcher.contexts[i];
  506.                 Firebug.NetMonitor.onToggleFilter(context, value);
  507.             }
  508.         }
  509.     },
  510.  
  511.     updateSelection: function(object)
  512.     {
  513.         if (!object)
  514.             return;
  515.  
  516.         var netProgress = this.context.netProgress;
  517.         var file = netProgress.getRequestFile(object.request);
  518.         if (!file)
  519.         {
  520.             for (var i=0; i<netProgress.requests.length; i++) {
  521.                 if (safeGetName(netProgress.requests[i]) == object.href) {
  522.                    file = netProgress.files[i];
  523.                    break;
  524.                 }
  525.             }
  526.         }
  527.  
  528.         if (file)
  529.         {
  530.             scrollIntoCenterView(file.row);
  531.             if (!hasClass(file.row, "opened"))
  532.                 NetRequestEntry.toggleHeadersRow(file.row);
  533.         }
  534.     },
  535.  
  536.     getPopupObject: function(target)
  537.     {
  538.         var header = getAncestorByClass(target, "netHeaderRow");
  539.         if (header)
  540.             return NetRequestTable;
  541.  
  542.         return Firebug.ActivablePanel.getPopupObject.apply(this, arguments);
  543.     },
  544.  
  545.     supportsObject: function(object)
  546.     {
  547.         return ((object instanceof SourceLink && object.type == "net") ? 2 : 0);
  548.     },
  549.  
  550.     getOptionsMenuItems: function()
  551.     {
  552.         return [
  553.             this.disableCacheOption()
  554.         ];
  555.     },
  556.  
  557.     disableCacheOption: function()
  558.     {
  559.         var BrowserCache = Firebug.NetMonitor.BrowserCache;
  560.         var disabled = !BrowserCache.isEnabled();
  561.         return {label: "net.option.Disable Browser Cache", type: "checkbox", checked: disabled,
  562.             command: bindFixed(BrowserCache.enable, BrowserCache, disabled) };
  563.     },
  564.  
  565.     getContextMenuItems: function(nada, target)
  566.     {
  567.         var items = [];
  568.  
  569.         var file = Firebug.getRepObject(target);
  570.         if (!file || !(file instanceof NetFile))
  571.             return items;
  572.  
  573.         var object = Firebug.getObjectByURL(this.context, file.href);
  574.         var isPost = Utils.isURLEncodedRequest(file, this.context);
  575.  
  576.         items.push(
  577.             {label: "CopyLocation", command: bindFixed(copyToClipboard, FBL, file.href) }
  578.         );
  579.  
  580.         if (isPost)
  581.         {
  582.             items.push(
  583.                 {label: "CopyLocationParameters", command: bindFixed(this.copyParams, this, file) }
  584.             );
  585.         }
  586.  
  587.         items.push(
  588.             {label: "CopyRequestHeaders",
  589.                 command: bindFixed(this.copyHeaders, this, file.requestHeaders) },
  590.             {label: "CopyResponseHeaders",
  591.                 command: bindFixed(this.copyHeaders, this, file.responseHeaders) }
  592.         );
  593.  
  594.         if (textFileCategories.hasOwnProperty(file.category))
  595.         {
  596.             items.push(
  597.                 {label: "CopyResponse", command: bindFixed(this.copyResponse, this, file) }
  598.             );
  599.         }
  600.  
  601.         items.push(
  602.             "-",
  603.             {label: "OpenInTab", command: bindFixed(this.openRequestInTab, this, file) }
  604.         );
  605.  
  606.         if (textFileCategories.hasOwnProperty(file.category))
  607.         {
  608.             items.push(
  609.                 {label: "Open Response In New Tab", command: bindFixed(this.openResponseInTab, this, file) }
  610.             );
  611.         }
  612.  
  613.         if (!file.loaded)
  614.         {
  615.             items.push(
  616.                 "-",
  617.                 {label: "StopLoading", command: bindFixed(this.stopLoading, this, file) }
  618.             );
  619.         }
  620.  
  621.         if (object)
  622.         {
  623.             var subItems = Firebug.chrome.getInspectMenuItems(object);
  624.             if (subItems.length)
  625.             {
  626.                 items.push("-");
  627.                 items.push.apply(items, subItems);
  628.             }
  629.         }
  630.  
  631.         if (file.isXHR)
  632.         {
  633.             var bp = this.context.netProgress.breakpoints.findBreakpoint(file.getFileURL());
  634.  
  635.             items.push(
  636.                 "-",
  637.                 {label: "net.label.Break On XHR", type: "checkbox", checked: !!bp,
  638.                     command: bindFixed(this.breakOnRequest, this, file) }
  639.             );
  640.  
  641.             if (bp)
  642.             {
  643.                 items.push(
  644.                     {label: "EditBreakpointCondition",
  645.                         command: bindFixed(this.editBreakpointCondition, this, file) }
  646.                 );
  647.             }
  648.         }
  649.  
  650.         return items;
  651.     },
  652.  
  653.     // Context menu commands
  654.     copyParams: function(file)
  655.     {
  656.         var text = Utils.getPostText(file, this.context, true);
  657.         var url = reEncodeURL(file, text, true);
  658.         copyToClipboard(url);
  659.     },
  660.  
  661.     copyHeaders: function(headers)
  662.     {
  663.         var lines = [];
  664.         if (headers)
  665.         {
  666.             for (var i = 0; i < headers.length; ++i)
  667.             {
  668.                 var header = headers[i];
  669.                 lines.push(header.name + ": " + header.value);
  670.             }
  671.         }
  672.  
  673.         var text = lines.join("\r\n");
  674.         copyToClipboard(text);
  675.     },
  676.  
  677.     copyResponse: function(file)
  678.     {
  679.         var allowDoublePost = Firebug.getPref(Firebug.prefDomain, "allowDoublePost");
  680.         if (!allowDoublePost && !file.cacheEntry)
  681.         {
  682.             if (!confirm("The response can be re-requested from the server, OK?"))
  683.                 return;
  684.         }
  685.  
  686.         // Copy response to the clipboard
  687.         copyToClipboard(Utils.getResponseText(file, this.context));
  688.     },
  689.  
  690.     openRequestInTab: function(file)
  691.     {
  692.         openNewTab(file.href, file.postText);
  693.     },
  694.  
  695.     openResponseInTab: function(file)
  696.     {
  697.         try
  698.         {
  699.             var response = Utils.getResponseText(file, this.context);
  700.             var inputStream = getInputStreamFromString(response);
  701.             var stream = CCIN("@mozilla.org/binaryinputstream;1", "nsIBinaryInputStream");
  702.             stream.setInputStream(inputStream);
  703.             var encodedResponse = btoa(stream.readBytes(stream.available()));
  704.             var dataURI = "data:" + file.request.contentType + ";base64," + encodedResponse;
  705.             gBrowser.selectedTab = gBrowser.addTab(dataURI);
  706.         }
  707.         catch (err)
  708.         {
  709.         }
  710.     },
  711.  
  712.     breakOnRequest: function(file)
  713.     {
  714.         if (!file.isXHR)
  715.             return;
  716.  
  717.         // Create new or remove an existing breakpoint.
  718.         var breakpoints = this.context.netProgress.breakpoints;
  719.         var url = file.getFileURL();
  720.         var bp = breakpoints.findBreakpoint(url);
  721.         if (bp)
  722.             breakpoints.removeBreakpoint(url);
  723.         else
  724.             breakpoints.addBreakpoint(url);
  725.  
  726.         this.enumerateRequests(function(currFile)
  727.         {
  728.             if (url != currFile.getFileURL())
  729.                 return;
  730.  
  731.             if (bp)
  732.                 currFile.row.removeAttribute("breakpoint");
  733.             else
  734.                 currFile.row.setAttribute("breakpoint", "true");
  735.         })
  736.     },
  737.  
  738.     stopLoading: function(file)
  739.     {
  740.         const NS_BINDING_ABORTED = 0x804b0002;
  741.  
  742.         file.request.cancel(NS_BINDING_ABORTED);
  743.     },
  744.  
  745.     // Support for xhr breakpoint conditions.
  746.     onContextMenu: function(event)
  747.     {
  748.         if (!hasClass(event.target, "sourceLine"))
  749.             return;
  750.  
  751.         var row = getAncestorByClass(event.target, "netRow");
  752.         if (!row)
  753.             return;
  754.  
  755.         var file = row.repObject;
  756.         var bp = this.context.netProgress.breakpoints.findBreakpoint(file.getFileURL());
  757.         if (!bp)
  758.             return;
  759.  
  760.         this.editBreakpointCondition(file);
  761.         cancelEvent(event);
  762.     },
  763.  
  764.     editBreakpointCondition: function(file)
  765.     {
  766.         var bp = this.context.netProgress.breakpoints.findBreakpoint(file.getFileURL());
  767.         if (!bp)
  768.             return;
  769.  
  770.         var condition = bp ? bp.condition : "";
  771.  
  772.         this.selectedSourceBox = this.panelNode;
  773.         Firebug.Editor.startEditing(file.row, condition);
  774.     },
  775.  
  776.     getEditor: function(target, value)
  777.     {
  778.         if (!this.conditionEditor)
  779.             this.conditionEditor = new Firebug.NetMonitor.ConditionEditor(this.document);
  780.  
  781.         return this.conditionEditor;
  782.     },
  783.  
  784.     // Support for activation.
  785.     disablePanel: function(module)
  786.     {
  787.         Firebug.ActivablePanel.disablePanel.apply(this, arguments);
  788.         this.table = null;
  789.     },
  790.  
  791.     breakOnNext: function(breaking)
  792.     {
  793.         this.context.breakOnXHR = breaking;
  794.     },
  795.  
  796.     shouldBreakOnNext: function()
  797.     {
  798.         return this.context.breakOnXHR;
  799.     },
  800.  
  801.     getBreakOnNextTooltip: function(enabled)
  802.     {
  803.         return (enabled ? $STR("net.Disable Break On XHR") : $STR("net.Break On XHR"));
  804.     },
  805.  
  806.     // Support for info tips.
  807.     showInfoTip: function(infoTip, target, x, y)
  808.     {
  809.         var row = getAncestorByClass(target, "netRow");
  810.         if (row && row.repObject)
  811.         {
  812.             if (getAncestorByClass(target, "netTotalSizeCol"))
  813.             {
  814.                 var infoTipURL = "netTotalSize";
  815.                 if (infoTipURL == this.infoTipURL)
  816.                     return true;
  817.  
  818.                 this.infoTipURL = infoTipURL;
  819.                 return this.populateTotalSizeInfoTip(infoTip, row);
  820.             }
  821.             else if (getAncestorByClass(target, "netSizeCol"))
  822.             {
  823.                 var infoTipURL = row.repObject.href + "-netsize";
  824.                 if (infoTipURL == this.infoTipURL && row.repObject == this.infoTipFile)
  825.                     return true;
  826.  
  827.                 this.infoTipURL = infoTipURL;
  828.                 this.infoTipFile = row.repObject;
  829.                 return this.populateSizeInfoTip(infoTip, row.repObject);
  830.             }
  831.             else if (getAncestorByClass(target, "netTimeCol"))
  832.             {
  833.                 var infoTipURL = row.repObject.href + "-nettime";
  834.                 if (infoTipURL == this.infoTipURL && row.repObject == this.infoTipFile)
  835.                     return true;
  836.  
  837.                 this.infoTipURL = infoTipURL;
  838.                 this.infoTipFile = row.repObject;
  839.                 return this.populateTimeInfoTip(infoTip, row.repObject);
  840.             }
  841.             else if (hasClass(row, "category-image") &&
  842.                 !getAncestorByClass(target, "netRowHeader"))
  843.             {
  844.                 var infoTipURL = row.repObject.href + "-image";
  845.                 if (infoTipURL == this.infoTipURL)
  846.                     return true;
  847.  
  848.                 this.infoTipURL = infoTipURL;
  849.                 return Firebug.InfoTip.populateImageInfoTip(infoTip, row.repObject.href);
  850.             }
  851.         }
  852.     },
  853.  
  854.     populateTimeInfoTip: function(infoTip, file)
  855.     {
  856.         Firebug.NetMonitor.TimeInfoTip.render(file, infoTip);
  857.         return true;
  858.     },
  859.  
  860.     populateSizeInfoTip: function(infoTip, file)
  861.     {
  862.         Firebug.NetMonitor.SizeInfoTip.render(file, infoTip);
  863.         return true;
  864.     },
  865.  
  866.     populateTotalSizeInfoTip: function(infoTip, row)
  867.     {
  868.         var totalSizeLabel = row.getElementsByClassName("netTotalSizeLabel").item(0);
  869.         var file = {size: totalSizeLabel.getAttribute("totalSize")};
  870.         Firebug.NetMonitor.SizeInfoTip.tag.replace({file: file}, infoTip);
  871.         return true;
  872.     },
  873.  
  874.     // Support for search within the panel.
  875.     getSearchOptionsMenuItems: function()
  876.     {
  877.         return [
  878.             Firebug.Search.searchOptionMenu("search.Case Sensitive", "searchCaseSensitive"),
  879.             //Firebug.Search.searchOptionMenu("search.net.Headers", "netSearchHeaders"),
  880.             //Firebug.Search.searchOptionMenu("search.net.Parameters", "netSearchParameters"),
  881.             Firebug.Search.searchOptionMenu("search.net.Response Bodies", "netSearchResponseBody")
  882.         ];
  883.     },
  884.  
  885.     search: function(text, reverse)
  886.     {
  887.         if (!text)
  888.         {
  889.             delete this.currentSearch;
  890.             return false;
  891.         }
  892.  
  893.         var row;
  894.         if (this.currentSearch && text == this.currentSearch.text)
  895.         {
  896.             row = this.currentSearch.findNext(true, false, reverse, Firebug.Search.isCaseSensitive(text));
  897.         }
  898.         else
  899.         {
  900.             this.currentSearch = new NetPanelSearch(this);
  901.             row = this.currentSearch.find(text, reverse, Firebug.Search.isCaseSensitive(text));
  902.         }
  903.  
  904.         if (row)
  905.         {
  906.             var sel = this.document.defaultView.getSelection();
  907.             sel.removeAllRanges();
  908.             sel.addRange(this.currentSearch.range);
  909.  
  910.             scrollIntoCenterView(row, this.panelNode);
  911.             dispatch([Firebug.A11yModel], 'onNetMatchFound', [this, text, row]);
  912.             return true;
  913.         }
  914.         else
  915.         {
  916.             dispatch([Firebug.A11yModel], 'onNetMatchFound', [this, text, null]);
  917.             return false;
  918.         }
  919.     },
  920.  
  921.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  922.  
  923.     updateFile: function(file)
  924.     {
  925.         if (!file.invalid)
  926.         {
  927.             file.invalid = true;
  928.             this.queue.push(file);
  929.         }
  930.     },
  931.  
  932.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  933.  
  934.     updateLayout: function()
  935.     {
  936.         if (!this.queue.length)
  937.             return;
  938.  
  939.         var rightNow = now();
  940.         var length = this.queue.length;
  941.  
  942.         if (this.panelNode.offsetHeight)
  943.             this.wasScrolledToBottom = isScrolledToBottom(this.panelNode);
  944.  
  945.         this.layout();
  946.  
  947.         if (this.wasScrolledToBottom)
  948.             scrollToBottom(this.panelNode);
  949.  
  950.     },
  951.  
  952.     layout: function()
  953.     {
  954.         if (!this.queue.length || !this.context.netProgress ||
  955.             !Firebug.NetMonitor.isAlwaysEnabled())
  956.             return;
  957.  
  958.         this.initLayout();
  959.  
  960.         var rightNow = now();
  961.         this.updateRowData(rightNow);
  962.         this.updateLogLimit(maxQueueRequests);
  963.         this.updateTimeline(rightNow);
  964.         this.updateSummaries(rightNow);
  965.     },
  966.  
  967.     initLayout: function()
  968.     {
  969.         if (!this.table)
  970.         {
  971.             var limitInfo = {
  972.                 totalCount: 0,
  973.                 limitPrefsTitle: $STRF("LimitPrefsTitle", [Firebug.prefDomain+".net.logLimit"])
  974.             };
  975.  
  976.             this.table = NetRequestTable.tableTag.append({}, this.panelNode);
  977.             this.limitRow = NetLimit.createRow(this.table.firstChild, limitInfo);
  978.             this.summaryRow =  NetRequestEntry.summaryTag.insertRows({}, this.table.lastChild.lastChild)[0];
  979.  
  980.             // Update visibility of columns according to the preferences
  981.             var hiddenCols = Firebug.getPref(Firebug.prefDomain, "net.hiddenColumns");
  982.             if (hiddenCols)
  983.                 this.table.setAttribute("hiddenCols", hiddenCols);
  984.         }
  985.     },
  986.  
  987.     updateRowData: function(rightNow)
  988.     {
  989.         var queue = this.queue;
  990.         this.queue = [];
  991.  
  992.         var phase;
  993.         var newFileData = [];
  994.  
  995.         for (var i = 0; i < queue.length; ++i)
  996.         {
  997.             var file = queue[i];
  998.             if (!file.phase)
  999.               continue;
  1000.  
  1001.             file.invalid = false;
  1002.  
  1003.             phase = this.calculateFileTimes(file, phase, rightNow);
  1004.  
  1005.             this.updateFileRow(file, newFileData);
  1006.             this.invalidatePhase(phase);
  1007.         }
  1008.  
  1009.         if (newFileData.length)
  1010.         {
  1011.             var tbody = this.table.firstChild;
  1012.             var lastRow = tbody.lastChild.previousSibling;
  1013.             this.insertRows(newFileData, lastRow);
  1014.         }
  1015.     },
  1016.  
  1017.     insertRows: function(files, lastRow)
  1018.     {
  1019.         var row = NetRequestEntry.fileTag.insertRows({files: files}, lastRow)[0];
  1020.  
  1021.         for (var i = 0; i < files.length; ++i)
  1022.         {
  1023.             var file = files[i].file;
  1024.             row.repObject = file;
  1025.             file.row = row;
  1026.  
  1027.             // Make sure a breakpoint is displayed.
  1028.             var breakpoints = this.context.netProgress.breakpoints;
  1029.             if (breakpoints && breakpoints.findBreakpoint(file.getFileURL()))
  1030.                 row.setAttribute("breakpoint", "true");
  1031.  
  1032.             // Allow customization of request entries in the list. A row is represented
  1033.             // by <TR> HTML element.
  1034.             dispatch(NetRequestTable.fbListeners, "onCreateRequestEntry", [this, row]);
  1035.  
  1036.             row = row.nextSibling;
  1037.         }
  1038.     },
  1039.  
  1040.     invalidatePhase: function(phase)
  1041.     {
  1042.         if (phase && !phase.invalidPhase)
  1043.         {
  1044.             phase.invalidPhase = true;
  1045.             this.invalidPhases = true;
  1046.         }
  1047.     },
  1048.  
  1049.     updateFileRow: function(file, newFileData)
  1050.     {
  1051.         var row = file.row;
  1052.         if (!row)
  1053.         {
  1054.             newFileData.push({
  1055.                 file: file,
  1056.                 offset: this.barOffset + "%",
  1057.                 width: this.barReceivingWidth + "%",
  1058.                 elapsed: file.loaded ? this.elapsed : -1
  1059.             });
  1060.         }
  1061.         else
  1062.         {
  1063.             var sizeLabel = row.childNodes[4].firstChild;
  1064.  
  1065.             var sizeText = NetRequestEntry.getSize(file);
  1066.  
  1067.             // Show also total downloaded size for requests in progress.
  1068.             if (file.totalReceived)
  1069.                 sizeText += " (" + formatSize(file.totalReceived) + ")";
  1070.  
  1071.             sizeLabel.firstChild.nodeValue = sizeText;
  1072.  
  1073.             var methodLabel = row.childNodes[2].firstChild;
  1074.             methodLabel.firstChild.nodeValue = NetRequestEntry.getStatus(file);
  1075.  
  1076.             var hrefLabel = row.childNodes[1].firstChild;
  1077.             hrefLabel.firstChild.nodeValue = NetRequestEntry.getHref(file);
  1078.  
  1079.             if (file.mimeType)
  1080.             {
  1081.                 // Force update category.
  1082.                 file.category = null;
  1083.                 for (var category in fileCategories)
  1084.                     removeClass(row, "category-" + category);
  1085.                 setClass(row, "category-" + Utils.getFileCategory(file));
  1086.             }
  1087.  
  1088.             if (file.responseHeaders)
  1089.                 setClass(row, "hasHeaders");
  1090.  
  1091.             if (file.fromCache)
  1092.                 setClass(row, "fromCache");
  1093.             else
  1094.                 removeClass(row, "fromCache");
  1095.  
  1096.             if (NetRequestEntry.isError(file))
  1097.                 setClass(row, "responseError");
  1098.             else
  1099.                 removeClass(row, "responseError");
  1100.  
  1101.             var timeLabel = row.childNodes[5].childNodes[1].lastChild.firstChild;
  1102.             timeLabel.innerHTML = NetRequestEntry.getElapsedTime({elapsed: this.elapsed});
  1103.  
  1104.             if (file.loaded)
  1105.                 setClass(row, "loaded");
  1106.             else
  1107.                 removeClass(row, "loaded");
  1108.  
  1109.             if (hasClass(row, "opened"))
  1110.             {
  1111.                 var netInfoBox = row.nextSibling.getElementsByClassName("netInfoBody").item(0);
  1112.                 NetInfoBody.updateInfo(netInfoBox, file, this.context);
  1113.             }
  1114.         }
  1115.     },
  1116.  
  1117.     updateTimeline: function(rightNow)
  1118.     {
  1119.         var tbody = this.table.firstChild;
  1120.  
  1121.         // XXXjoe Don't update rows whose phase is done and layed out already
  1122.         var phase;
  1123.         for (var row = tbody.firstChild; row; row = row.nextSibling)
  1124.         {
  1125.             var file = row.repObject;
  1126.  
  1127.             // Some rows aren't associated with a file (e.g. header, sumarry).
  1128.             if (!file)
  1129.                 continue;
  1130.  
  1131.             if (!file.loaded)
  1132.                 continue;
  1133.  
  1134.             phase = this.calculateFileTimes(file, phase, rightNow);
  1135.  
  1136.             // Get bar nodes
  1137.             var blockingBar = row.childNodes[5].childNodes[1].childNodes[1];
  1138.             var resolvingBar = blockingBar.nextSibling;
  1139.             var connectingBar = resolvingBar.nextSibling;
  1140.             var sendingBar = connectingBar.nextSibling;
  1141.             var waitingBar = sendingBar.nextSibling;
  1142.             var contentLoadBar = waitingBar.nextSibling;
  1143.             var windowLoadBar = contentLoadBar.nextSibling;
  1144.             var receivingBar = windowLoadBar.nextSibling;
  1145.  
  1146.             // All bars starts at the beginning
  1147.             resolvingBar.style.left = connectingBar.style.left = sendingBar.style.left =
  1148.                 blockingBar.style.left =
  1149.                 waitingBar.style.left = receivingBar.style.left = this.barOffset + "%";
  1150.  
  1151.             // Sets width of all bars (using style). The width is computed according to measured timing.
  1152.             blockingBar.style.width = this.barBlockingWidth + "%";
  1153.             resolvingBar.style.width = this.barResolvingWidth + "%";
  1154.             connectingBar.style.width = this.barConnectingWidth + "%";
  1155.             sendingBar.style.width = this.barSendingWidth + "%";
  1156.             waitingBar.style.width = this.barWaitingWidth + "%";
  1157.             receivingBar.style.width = this.barReceivingWidth + "%";
  1158.  
  1159.             if (this.contentLoadBarOffset) {
  1160.                 contentLoadBar.style.left = this.contentLoadBarOffset + "%";
  1161.                 contentLoadBar.style.display = "block";
  1162.                 this.contentLoadBarOffset = null;
  1163.             }
  1164.  
  1165.             if (this.windowLoadBarOffset) {
  1166.                 windowLoadBar.style.left = this.windowLoadBarOffset + "%";
  1167.                 windowLoadBar.style.display = "block";
  1168.                 this.windowLoadBarOffset = null;
  1169.             }
  1170.         }
  1171.     },
  1172.  
  1173.     calculateFileTimes: function(file, phase, rightNow)
  1174.     {
  1175.         var phases = this.context.netProgress.phases;
  1176.  
  1177.         if (phase != file.phase)
  1178.         {
  1179.             phase = file.phase;
  1180.             this.phaseStartTime = phase.startTime;
  1181.             this.phaseEndTime = phase.endTime ? phase.endTime : rightNow;
  1182.  
  1183.             // End of the first phase has to respect even the window "onload" event time, which
  1184.             // can occur after the last received file. This sets the extent of the timeline so,
  1185.             // the windowLoadBar is visible.
  1186.             if (phase.windowLoadTime && this.phaseEndTime < phase.windowLoadTime)
  1187.                 this.phaseEndTime = phase.windowLoadTime;
  1188.  
  1189.             this.phaseElapsed = this.phaseEndTime - phase.startTime;
  1190.         }
  1191.  
  1192.         var elapsed = file.loaded ? file.endTime - file.startTime : 0; /*this.phaseEndTime - file.startTime*/
  1193.         this.barOffset = Math.floor(((file.startTime-this.phaseStartTime)/this.phaseElapsed) * 100);
  1194.  
  1195.         var blockingEnd = (file.sendingTime != file.startTime) ? file.sendingTime : file.waitingForTime;
  1196.  
  1197.         this.barResolvingWidth = Math.round(((file.connectingTime - file.startTime) / this.phaseElapsed) * 100);
  1198.         this.barConnectingWidth = Math.round(((file.connectedTime - file.startTime) / this.phaseElapsed) * 100);
  1199.         this.barBlockingWidth = Math.round(((blockingEnd - file.startTime) / this.phaseElapsed) * 100);
  1200.         this.barSendingWidth = Math.round(((file.waitingForTime - file.startTime) / this.phaseElapsed) * 100);
  1201.         this.barWaitingWidth = Math.round(((file.respondedTime - file.startTime) / this.phaseElapsed) * 100);
  1202.         this.barReceivingWidth = Math.round((elapsed / this.phaseElapsed) * 100);
  1203.  
  1204.         // Total request time doesn't include the time spent in queue.
  1205.         // xxxHonza: since all phases are now graphically distinguished it's easy to
  1206.         // see blocking requests. It's make sense to display the real total time now.
  1207.         this.elapsed = elapsed/* - (file.sendingTime - file.connectedTime)*/;
  1208.  
  1209.         // The nspr timer doesn't have 1ms precision, so it can happen that entire
  1210.         // request is executed in l ms (so the total is zero). Let's display at least
  1211.         // one bar in such a case so the timeline is visible.
  1212.         if (this.elapsed <= 0)
  1213.             this.barReceivingWidth = "1";
  1214.  
  1215.         // Compute also offset for the contentLoadBar and windowLoadBar, which are
  1216.         // displayed for the first phase.
  1217.         if (phase.contentLoadTime)
  1218.             this.contentLoadBarOffset = Math.floor(((phase.contentLoadTime-this.phaseStartTime)/this.phaseElapsed) * 100);
  1219.  
  1220.         if (phase.windowLoadTime)
  1221.             this.windowLoadBarOffset = Math.floor(((phase.windowLoadTime-this.phaseStartTime)/this.phaseElapsed) * 100);
  1222.  
  1223.         return phase;
  1224.     },
  1225.  
  1226.     updateSummaries: function(rightNow, updateAll)
  1227.     {
  1228.         if (!this.invalidPhases && !updateAll)
  1229.             return;
  1230.  
  1231.         this.invalidPhases = false;
  1232.  
  1233.         var phases = this.context.netProgress.phases;
  1234.         if (!phases.length)
  1235.             return;
  1236.  
  1237.         var fileCount = 0, totalSize = 0, cachedSize = 0, totalTime = 0;
  1238.         for (var i = 0; i < phases.length; ++i)
  1239.         {
  1240.             var phase = phases[i];
  1241.             phase.invalidPhase = false;
  1242.  
  1243.             var summary = this.summarizePhase(phase, rightNow);
  1244.             fileCount += summary.fileCount;
  1245.             totalSize += summary.totalSize;
  1246.             cachedSize += summary.cachedSize;
  1247.             totalTime += summary.totalTime
  1248.         }
  1249.  
  1250.         var row = this.summaryRow;
  1251.         if (!row)
  1252.             return;
  1253.  
  1254.         var countLabel = row.childNodes[1].firstChild;
  1255.         countLabel.firstChild.nodeValue = $STRP("plural.Request_Count", [fileCount]);
  1256.  
  1257.         var sizeLabel = row.childNodes[4].firstChild;
  1258.         sizeLabel.setAttribute("totalSize", totalSize);
  1259.         sizeLabel.firstChild.nodeValue = NetRequestEntry.formatSize(totalSize);
  1260.  
  1261.         var cacheSizeLabel = row.lastChild.firstChild.firstChild;
  1262.         cacheSizeLabel.setAttribute("collapsed", cachedSize == 0);
  1263.         cacheSizeLabel.childNodes[1].firstChild.nodeValue =
  1264.             NetRequestEntry.formatSize(cachedSize);
  1265.  
  1266.         var timeLabel = row.lastChild.firstChild.lastChild.firstChild;
  1267.         var timeText = NetRequestEntry.formatTime(totalTime);
  1268.         var firstPhase = phases[0];
  1269.         if (firstPhase.windowLoadTime)
  1270.         {
  1271.             var loadTime = firstPhase.windowLoadTime - firstPhase.startTime;
  1272.             // xxxHonza: localization?
  1273.             timeText += " (onload: " + NetRequestEntry.formatTime(loadTime) + ")";
  1274.         }
  1275.  
  1276.         timeLabel.innerHTML = timeText;
  1277.     },
  1278.  
  1279.     summarizePhase: function(phase, rightNow)
  1280.     {
  1281.         var cachedSize = 0, totalSize = 0;
  1282.  
  1283.         var category = Firebug.netFilterCategory;
  1284.         if (category == "all")
  1285.             category = null;
  1286.  
  1287.         var fileCount = 0;
  1288.         var minTime = 0, maxTime = 0;
  1289.  
  1290.         for (var i=0; i<phase.files.length; i++)
  1291.         {
  1292.             var file = phase.files[i];
  1293.  
  1294.             if (!category || file.category == category)
  1295.             {
  1296.                 if (file.loaded)
  1297.                 {
  1298.                     ++fileCount;
  1299.  
  1300.                     if (file.size > 0)
  1301.                     {
  1302.                         totalSize += file.size;
  1303.                         if (file.fromCache)
  1304.                             cachedSize += file.size;
  1305.                     }
  1306.  
  1307.                     if (!minTime || file.startTime < minTime)
  1308.                         minTime = file.startTime;
  1309.                     if (file.endTime > maxTime)
  1310.                         maxTime = file.endTime;
  1311.                 }
  1312.             }
  1313.         }
  1314.  
  1315.         var totalTime = maxTime - minTime;
  1316.         return {cachedSize: cachedSize, totalSize: totalSize, totalTime: totalTime,
  1317.                 fileCount: fileCount}
  1318.     },
  1319.  
  1320.     updateLogLimit: function(limit)
  1321.     {
  1322.         var netProgress = this.context.netProgress;
  1323.  
  1324.         if (!netProgress)  // XXXjjb Honza, please check, I guess we are getting here with the context not setup
  1325.         {
  1326.             return;
  1327.         }
  1328.  
  1329.         // Must be positive number;
  1330.         limit = Math.max(0, limit);
  1331.  
  1332.         var filesLength = netProgress.files.length;
  1333.         if (!filesLength || filesLength <= limit)
  1334.             return;
  1335.  
  1336.         // Remove old requests.
  1337.         var removeCount = Math.max(0, filesLength - limit);
  1338.         for (var i=0; i<removeCount; i++)
  1339.         {
  1340.             var file = netProgress.files[0];
  1341.             this.removeLogEntry(file);
  1342.  
  1343.             // Remove the file occurrence from the queue.
  1344.             for (var j=0; j<this.queue.length; j++)
  1345.             {
  1346.                 if (this.queue[j] == file) {
  1347.                     this.queue.splice(j, 1);
  1348.                     j--;
  1349.                 }
  1350.             }
  1351.         }
  1352.     },
  1353.  
  1354.     removeLogEntry: function(file, noInfo)
  1355.     {
  1356.         if (!this.removeFile(file))
  1357.             return;
  1358.  
  1359.         if (!this.table || !this.table.firstChild)
  1360.             return;
  1361.  
  1362.         if (file.row)
  1363.         {
  1364.             // The file is loaded and there is a row that has to be removed from the UI.
  1365.             var tbody = this.table.firstChild;
  1366.             clearDomplate(file.row);
  1367.             tbody.removeChild(file.row);
  1368.         }
  1369.  
  1370.         if (noInfo || !this.limitRow)
  1371.             return;
  1372.  
  1373.         this.limitRow.limitInfo.totalCount++;
  1374.  
  1375.         NetLimit.updateCounter(this.limitRow);
  1376.  
  1377.         //if (netProgress.currentPhase == file.phase)
  1378.         //  netProgress.currentPhase = null;
  1379.     },
  1380.  
  1381.     removeFile: function(file)
  1382.     {
  1383.         var netProgress = this.context.netProgress;
  1384.         var index = netProgress.files.indexOf(file);
  1385.         if (index == -1)
  1386.             return false;
  1387.  
  1388.         netProgress.files.splice(index, 1);
  1389.         netProgress.requests.splice(index, 1);
  1390.  
  1391.         // Don't forget to remove the phase whose last file has been removed.
  1392.         var phase = file.phase;
  1393.  
  1394.         // xxxHonza: This needs to be examined yet. Looks like the queue contains
  1395.         // requests from the previous page. When flushed the requestedFile isn't called
  1396.         // and the phase is not set.
  1397.         if (!phase)
  1398.             return true;
  1399.  
  1400.         phase.removeFile(file);
  1401.         if (!phase.files.length)
  1402.         {
  1403.             remove(netProgress.phases, phase);
  1404.  
  1405.             if (netProgress.currentPhase == phase)
  1406.                 netProgress.currentPhase = null;
  1407.         }
  1408.  
  1409.         return true;
  1410.     },
  1411.  
  1412.     insertActivationMessage: function()
  1413.     {
  1414.         if (!Firebug.NetMonitor.isAlwaysEnabled())
  1415.             return;
  1416.  
  1417.         // Make sure the basic structure of the table panel is there.
  1418.         this.initLayout();
  1419.  
  1420.         // Get the last request row before summary row.
  1421.         var tbody = this.table.firstChild;
  1422.         var lastRow = tbody.lastChild.previousSibling;
  1423.  
  1424.         // Insert an activation message (if the last row isn't the message already);
  1425.         if (hasClass(lastRow, "netActivationRow"))
  1426.             return;
  1427.  
  1428.         var message = NetRequestEntry.activationTag.insertRows({}, lastRow)[0];
  1429.  
  1430.     },
  1431.  
  1432.     enumerateRequests: function(fn)
  1433.     {
  1434.         if (!this.table)
  1435.             return;
  1436.  
  1437.         var rows = this.table.getElementsByClassName("netRow");
  1438.         for (var i=0; i<rows.length; i++)
  1439.         {
  1440.             var row = rows[i];
  1441.             var pageRow = hasClass(row, "netPageRow");
  1442.  
  1443.             if (hasClass(row, "collapsed") && !pageRow)
  1444.                 continue;
  1445.  
  1446.             if (hasClass(row, "history"))
  1447.                 continue;
  1448.  
  1449.             // Export also history. These requests can be collpased and so not visible.
  1450.             if (row.files)
  1451.             {
  1452.                 for (var j=0; j<row.files.length; j++)
  1453.                     fn(row.files[j].file);
  1454.             }
  1455.  
  1456.             var file = Firebug.getRepObject(row);
  1457.             if (file)
  1458.                 fn(file);
  1459.         }
  1460.     },
  1461.  
  1462.     setFilter: function(filterCategory)
  1463.     {
  1464.         this.filterCategory = filterCategory;
  1465.  
  1466.         var panelNode = this.panelNode;
  1467.         for (var category in fileCategories)
  1468.         {
  1469.             if (filterCategory != "all" && category != filterCategory)
  1470.                 setClass(panelNode, "hideCategory-"+category);
  1471.             else
  1472.                 removeClass(panelNode, "hideCategory-"+category);
  1473.         }
  1474.     },
  1475.  
  1476.     clear: function()
  1477.     {
  1478.         clearNode(this.panelNode);
  1479.  
  1480.         this.table = null;
  1481.         this.summaryRow = null;
  1482.         this.limitRow = null;
  1483.  
  1484.         this.queue = [];
  1485.         this.invalidPhases = false;
  1486.  
  1487.         if (this.context.netProgress)
  1488.             this.context.netProgress.clear();
  1489.  
  1490.     },
  1491. });
  1492.  
  1493. // ************************************************************************************************
  1494.  
  1495. /**
  1496.  * @domplate Represents a template that is used to render basic content of the net panel.
  1497.  */
  1498. Firebug.NetMonitor.NetRequestTable = domplate(Firebug.Rep, new Firebug.Listener(),
  1499. {
  1500.     inspectable: false,
  1501.  
  1502.     tableTag:
  1503.  
  1504.         TABLE({"class": "netTable", cellpadding: 0, cellspacing: 0, hiddenCols: "", "role": "treegrid"},
  1505.             TBODY({"class": "netTableBody", "role" : "presentation"},
  1506.                 TR({"class": "netHeaderRow netRow focusRow outerFocusRow", onclick: "$onClickHeader", "role": "row"},
  1507.                     TD({id: "netBreakpointBar", width: "1%", "class": "netHeaderCell",
  1508.                         "role": "columnheader"},
  1509.                         " "
  1510.                     ),
  1511.                     TD({id: "netHrefCol", width: "18%", "class": "netHeaderCell alphaValue a11yFocus",
  1512.                         "role": "columnheader"},
  1513.                         DIV({"class": "netHeaderCellBox",
  1514.                         title: $STR("net.header.URL Tooltip")},
  1515.                         $STR("net.header.URL"))
  1516.                     ),
  1517.                     TD({id: "netStatusCol", width: "12%", "class": "netHeaderCell alphaValue a11yFocus",
  1518.                         "role": "columnheader"},
  1519.                         DIV({"class": "netHeaderCellBox",
  1520.                         title: $STR("net.header.Status Tooltip")},
  1521.                         $STR("net.header.Status"))
  1522.                     ),
  1523.                     TD({id: "netDomainCol", width: "12%", "class": "netHeaderCell alphaValue a11yFocus",
  1524.                         "role": "columnheader"},
  1525.                         DIV({"class": "netHeaderCellBox",
  1526.                         title: $STR("net.header.Domain Tooltip")},
  1527.                         $STR("net.header.Domain"))
  1528.                     ),
  1529.                     TD({id: "netSizeCol", width: "4%", "class": "netHeaderCell a11yFocus",
  1530.                         "role": "columnheader"},
  1531.                         DIV({"class": "netHeaderCellBox",
  1532.                         title: $STR("net.header.Size Tooltip")},
  1533.                         $STR("net.header.Size"))
  1534.                     ),
  1535.                     TD({id: "netTimeCol", width: "53%", "class": "netHeaderCell a11yFocus",
  1536.                         "role": "columnheader"},
  1537.                         DIV({"class": "netHeaderCellBox",
  1538.                         title: $STR("net.header.Timeline Tooltip")},
  1539.                         $STR("net.header.Timeline"))
  1540.                     )
  1541.                 )
  1542.             )
  1543.         ),
  1544.  
  1545.     onClickHeader: function(event)
  1546.     {
  1547.         if (!isLeftClick(event) && !(event.type == "keypress" && event.keyCode == 13))
  1548.             return;
  1549.  
  1550.         var table = getAncestorByClass(event.target, "netTable");
  1551.         var column = getAncestorByClass(event.target, "netHeaderCell");
  1552.         this.sortColumn(table, column);
  1553.     },
  1554.  
  1555.     sortColumn: function(table, col, direction)
  1556.     {
  1557.         if (!col)
  1558.             return;
  1559.  
  1560.         var numerical = !hasClass(col, "alphaValue");
  1561.  
  1562.         var colIndex = 0;
  1563.         for (col = col.previousSibling; col; col = col.previousSibling)
  1564.             ++colIndex;
  1565.  
  1566.         // the first breakpoint bar column is not sortable.
  1567.         if (colIndex == 0)
  1568.             return;
  1569.  
  1570.         this.sort(table, colIndex, numerical, direction);
  1571.     },
  1572.  
  1573.     sort: function(table, colIndex, numerical, direction)
  1574.     {
  1575.         var tbody = table.lastChild;
  1576.         var headerRow = tbody.firstChild;
  1577.  
  1578.         // Remove class from the currently sorted column
  1579.         var headerSorted = getChildByClass(headerRow, "netHeaderSorted");
  1580.         removeClass(headerSorted, "netHeaderSorted");
  1581.         if (headerSorted)
  1582.             headerSorted.removeAttribute("aria-sort");
  1583.  
  1584.         // Mark new column as sorted.
  1585.         var header = headerRow.childNodes[colIndex];
  1586.         setClass(header, "netHeaderSorted");
  1587.         // If the column is already using required sort direction, bubble out.
  1588.         if ((direction == "desc" && header.sorted == 1) ||
  1589.             (direction == "asc" && header.sorted == -1))
  1590.             return;
  1591.         if (header)
  1592.             header.setAttribute("aria-sort", header.sorted === -1 ? "descending" : "ascending");
  1593.         var colID = header.getAttribute("id");
  1594.  
  1595.         var values = [];
  1596.         for (var row = tbody.childNodes[1]; row; row = row.nextSibling)
  1597.         {
  1598.             if (!row.repObject)
  1599.                 continue;
  1600.  
  1601.             if (hasClass(row, "history"))
  1602.                 continue;
  1603.  
  1604.             var cell = row.childNodes[colIndex];
  1605.             var value = numerical ? parseFloat(cell.textContent) : cell.textContent;
  1606.  
  1607.             if (colID == "netTimeCol")
  1608.                 value = row.repObject.startTime;
  1609.             else if (colID == "netSizeCol")
  1610.                 value = row.repObject.size;
  1611.  
  1612.             if (hasClass(row, "opened"))
  1613.             {
  1614.                 var netInfoRow = row.nextSibling;
  1615.                 values.push({row: row, value: value, info: netInfoRow});
  1616.                 row = netInfoRow;
  1617.             }
  1618.             else
  1619.             {
  1620.                 values.push({row: row, value: value});
  1621.             }
  1622.         }
  1623.  
  1624.         values.sort(function(a, b) { return a.value < b.value ? -1 : 1; });
  1625.  
  1626.         if ((header.sorted && header.sorted == 1) || (!header.sorted && direction == "asc"))
  1627.         {
  1628.             removeClass(header, "sortedDescending");
  1629.             setClass(header, "sortedAscending");
  1630.             header.sorted = -1;
  1631.  
  1632.             for (var i = 0; i < values.length; ++i)
  1633.             {
  1634.                 tbody.appendChild(values[i].row);
  1635.                 if (values[i].info)
  1636.                     tbody.appendChild(values[i].info);
  1637.             }
  1638.         }
  1639.         else
  1640.         {
  1641.             removeClass(header, "sortedAscending");
  1642.             setClass(header, "sortedDescending");
  1643.  
  1644.             header.sorted = 1;
  1645.  
  1646.             for (var i = values.length-1; i >= 0; --i)
  1647.             {
  1648.                 tbody.appendChild(values[i].row);
  1649.                 if (values[i].info)
  1650.                     tbody.appendChild(values[i].info);
  1651.             }
  1652.         }
  1653.  
  1654.         // Make sure the summary row is again at the end.
  1655.         var summaryRow = tbody.getElementsByClassName("netSummaryRow").item(0);
  1656.         tbody.appendChild(summaryRow);
  1657.     },
  1658.  
  1659.     supportsObject: function(object)
  1660.     {
  1661.         return (object == this);
  1662.     },
  1663.  
  1664.     /**
  1665.      * Provides menu items for header context menu.
  1666.      */
  1667.     getContextMenuItems: function(object, target, context)
  1668.     {
  1669.         var popup = $("fbContextMenu");
  1670.         if (popup.firstChild && popup.firstChild.getAttribute("command") == "cmd_copy")
  1671.             popup.removeChild(popup.firstChild);
  1672.  
  1673.         var items = [];
  1674.  
  1675.         // Iterate over all columns and create a menu item for each.
  1676.         var table = context.getPanel(panelName, true).table;
  1677.         var hiddenCols = table.getAttribute("hiddenCols");
  1678.  
  1679.         var lastVisibleIndex;
  1680.         var visibleColCount = 0;
  1681.  
  1682.         // Iterate all columns except of the first one for breakpoints.
  1683.         var header = getAncestorByClass(target, "netHeaderRow");
  1684.         var columns = cloneArray(header.childNodes);
  1685.         columns.shift();
  1686.         for (var i=0; i<columns.length; i++)
  1687.         {
  1688.             var column = columns[i];
  1689.             var visible = (hiddenCols.indexOf(column.id) == -1);
  1690.  
  1691.             items.push({
  1692.                 label: column.textContent,
  1693.                 type: "checkbox",
  1694.                 checked: visible,
  1695.                 nol10n: true,
  1696.                 command: bindFixed(this.onShowColumn, this, context, column.id)
  1697.             });
  1698.  
  1699.             if (visible)
  1700.             {
  1701.                 lastVisibleIndex = i;
  1702.                 visibleColCount++;
  1703.             }
  1704.         }
  1705.  
  1706.         // If the last column is visible, disable its menu item.
  1707.         if (visibleColCount == 1)
  1708.             items[lastVisibleIndex].disabled = true;
  1709.  
  1710.         items.push("-");
  1711.         items.push({
  1712.             label: $STR("net.header.Reset_Header"),
  1713.             nol10n: true,
  1714.             command: bindFixed(this.onResetColumns, this, context)
  1715.         });
  1716.  
  1717.         return items;
  1718.     },
  1719.  
  1720.     onShowColumn: function(context, colId)
  1721.     {
  1722.         var table = context.getPanel(panelName, true).table;
  1723.         var hiddenCols = table.getAttribute("hiddenCols");
  1724.  
  1725.         // If the column is already presented in the list of hidden columns,
  1726.         // remove it, otherwise append.
  1727.         var index = hiddenCols.indexOf(colId);
  1728.         if (index >= 0)
  1729.         {
  1730.             table.setAttribute("hiddenCols", hiddenCols.substr(0,index-1) +
  1731.                 hiddenCols.substr(index+colId.length));
  1732.         }
  1733.         else
  1734.         {
  1735.             table.setAttribute("hiddenCols", hiddenCols + " " + colId);
  1736.         }
  1737.  
  1738.         // Store current state into the preferences.
  1739.         Firebug.setPref(Firebug.prefDomain, "net.hiddenColumns", table.getAttribute("hiddenCols"));
  1740.     },
  1741.  
  1742.     onResetColumns: function(context)
  1743.     {
  1744.         var panel = context.getPanel(panelName, true);
  1745.         var header = panel.panelNode.getElementsByClassName("netHeaderRow").item(0);
  1746.  
  1747.         // Reset widths
  1748.         var columns = header.childNodes;
  1749.         for (var i=0; i<columns.length; i++)
  1750.         {
  1751.             var col = columns[i];
  1752.             if (col.style)
  1753.                 col.style.width = "";
  1754.         }
  1755.  
  1756.         // Reset visibility. Only the Status column is hidden by default.
  1757.         panel.table.setAttribute("hiddenCols", "colStatus");
  1758.         Firebug.setPref(Firebug.prefDomain, "net.hiddenColumns", "colStatus");
  1759.     },
  1760. });
  1761.  
  1762. var NetRequestTable = Firebug.NetMonitor.NetRequestTable;
  1763.  
  1764. // ************************************************************************************************
  1765.  
  1766. /**
  1767.  * @domplate Represents a template that is used to render net panel entries.
  1768.  */
  1769. Firebug.NetMonitor.NetRequestEntry = domplate(Firebug.Rep, new Firebug.Listener(),
  1770. {
  1771.     fileTag:
  1772.         FOR("file", "$files",
  1773.             TR({"class": "netRow $file.file|getCategory focusRow outerFocusRow",
  1774.                 onclick: "$onClick", "role": "row", "aria-expanded": "false",
  1775.                 $hasHeaders: "$file.file|hasResponseHeaders",
  1776.                 $history: "$file.file.history",
  1777.                 $loaded: "$file.file.loaded",
  1778.                 $responseError: "$file.file|isError",
  1779.                 $fromCache: "$file.file.fromCache",
  1780.                 $inFrame: "$file.file|getInFrame"},
  1781.                 TD({"class": "netCol"},
  1782.                    DIV({"class": "sourceLine netRowHeader",
  1783.                    onclick: "$onClickRowHeader"},
  1784.                         " "
  1785.                    )
  1786.                 ),
  1787.                 TD({"class": "netHrefCol netCol a11yFocus", "role": "rowheader"},
  1788.                     DIV({"class": "netHrefLabel netLabel",
  1789.                          style: "margin-left: $file.file|getIndent\\px"},
  1790.                         "$file.file|getHref"
  1791.                     ),
  1792.                     DIV({"class": "netFullHrefLabel netHrefLabel",
  1793.                          style: "margin-left: $file.file|getIndent\\px"},
  1794.                         "$file.file.href"
  1795.                     )
  1796.                 ),
  1797.                 TD({"class": "netStatusCol netCol a11yFocus", "role": "gridcell"},
  1798.                     DIV({"class": "netStatusLabel netLabel"}, "$file.file|getStatus")
  1799.                 ),
  1800.                 TD({"class": "netDomainCol netCol a11yFocus", "role": "gridcell" },
  1801.                     DIV({"class": "netDomainLabel netLabel"}, "$file.file|getDomain")
  1802.                 ),
  1803.                 TD({"class": "netSizeCol netCol a11yFocus", "role": "gridcell", "aria-describedby": "fbNetSizeInfoTip"},
  1804.                     DIV({"class": "netSizeLabel netLabel"}, "$file.file|getSize")
  1805.                 ),
  1806.                 TD({"class": "netTimeCol netCol a11yFocus", "role": "gridcell", "aria-describedby": "fbNetTimeInfoTip"  },
  1807.                     DIV({"class": "netLoadingIcon"}),
  1808.                     DIV({"class": "netBar"},
  1809.                         " ",
  1810.                         DIV({"class": "netBlockingBar", style: "left: $file.offset"}),
  1811.                         DIV({"class": "netResolvingBar", style: "left: $file.offset"}),
  1812.                         DIV({"class": "netConnectingBar", style: "left: $file.offset"}),
  1813.                         DIV({"class": "netSendingBar", style: "left: $file.offset"}),
  1814.                         DIV({"class": "netWaitingBar", style: "left: $file.offset"}),
  1815.                         DIV({"class": "netContentLoadBar", style: "left: $file.offset"}),
  1816.                         DIV({"class": "netWindowLoadBar", style: "left: $file.offset"}),
  1817.                         DIV({"class": "netReceivingBar", style: "left: $file.offset; width: $file.width"},
  1818.                             SPAN({"class": "netTimeLabel"}, "$file|getElapsedTime")
  1819.                         )
  1820.                     )
  1821.                 )
  1822.             )
  1823.         ),
  1824.  
  1825.     headTag:
  1826.         TR({"class": "netHeadRow"},
  1827.             TD({"class": "netHeadCol", colspan: 6},
  1828.                 DIV({"class": "netHeadLabel"}, "$doc.rootFile.href")
  1829.             )
  1830.         ),
  1831.  
  1832.     netInfoTag:
  1833.         TR({"class": "netInfoRow outerFocusRow", "role" : "row"},
  1834.             TD({"class": "sourceLine netRowHeader"}),
  1835.             TD({"class": "netInfoCol", colspan: 5, "role" : "gridcell"})
  1836.         ),
  1837.  
  1838.     activationTag:
  1839.         TR({"class": "netRow netActivationRow"},
  1840.             TD({"class": "netCol netActivationLabel", colspan: 6, "role": "status"},
  1841.                 $STR("net.ActivationMessage")
  1842.             )
  1843.         ),
  1844.  
  1845.     summaryTag:
  1846.         TR({"class": "netRow netSummaryRow focusRow outerFocusRow", "role": "row", "aria-live": "polite"},
  1847.             TD({"class": "netCol"}, " "),
  1848.             TD({"class": "netCol netHrefCol a11yFocus", "role" : "rowheader"},
  1849.                 DIV({"class": "netCountLabel netSummaryLabel"}, "-")
  1850.             ),
  1851.             TD({"class": "netCol netStatusCol a11yFocus", "role" : "gridcell"}),
  1852.             TD({"class": "netCol netDomainCol a11yFocus", "role" : "gridcell"}),
  1853.             TD({"class": "netTotalSizeCol netCol netSizeCol a11yFocus", "role" : "gridcell"},
  1854.                 DIV({"class": "netTotalSizeLabel netSummaryLabel"}, "0KB")
  1855.             ),
  1856.             TD({"class": "netTotalTimeCol netCol netTimeCol a11yFocus", "role" : "gridcell"},
  1857.                 DIV({"class": "netSummaryBar", style: "width: 100%"},
  1858.                     DIV({"class": "netCacheSizeLabel netSummaryLabel", collapsed: "true"},
  1859.                         "(",
  1860.                         SPAN("0KB"),
  1861.                         SPAN(" " + $STR("FromCache")),
  1862.                         ")"
  1863.                     ),
  1864.                     DIV({"class": "netTimeBar"},
  1865.                         SPAN({"class": "netTotalTimeLabel netSummaryLabel"}, "0ms")
  1866.                     )
  1867.                 )
  1868.             )
  1869.         ),
  1870.  
  1871.     onClickRowHeader: function(event)
  1872.     {
  1873.         cancelEvent(event);
  1874.  
  1875.         var rowHeader = event.target;
  1876.         if (!hasClass(rowHeader, "netRowHeader"))
  1877.             return;
  1878.  
  1879.         var row = getAncestorByClass(event.target, "netRow");
  1880.         if (!row)
  1881.             return;
  1882.  
  1883.         var context = Firebug.getElementPanel(row).context;
  1884.         var panel = context.getPanel(panelName, true);
  1885.         if (panel)
  1886.             panel.breakOnRequest(row.repObject);
  1887.     },
  1888.  
  1889.     onClick: function(event)
  1890.     {
  1891.         if (isLeftClick(event))
  1892.         {
  1893.             var row = getAncestorByClass(event.target, "netRow");
  1894.             if (row)
  1895.             {
  1896.                 // Click on the rowHeader element inserts a breakpoint.
  1897.                 if (getAncestorByClass(event.target, "netRowHeader"))
  1898.                     return;
  1899.  
  1900.                 this.toggleHeadersRow(row);
  1901.                 cancelEvent(event);
  1902.             }
  1903.         }
  1904.     },
  1905.  
  1906.     toggleHeadersRow: function(row)
  1907.     {
  1908.         if (!hasClass(row, "hasHeaders"))
  1909.             return;
  1910.  
  1911.         var file = row.repObject;
  1912.  
  1913.         toggleClass(row, "opened");
  1914.         if (hasClass(row, "opened"))
  1915.         {
  1916.             var netInfoRow = this.netInfoTag.insertRows({}, row)[0];
  1917.             var netInfoCol = netInfoRow.getElementsByClassName("netInfoCol").item(0);
  1918.             var netInfoBox = NetInfoBody.tag.replace({file: file}, netInfoCol);
  1919.  
  1920.             // Notify listeners so additional tabs can be created.
  1921.             dispatch(NetInfoBody.fbListeners, "initTabBody", [netInfoBox, file]);
  1922.  
  1923.             NetInfoBody.selectTabByName(netInfoBox, "Headers");
  1924.             var category = Utils.getFileCategory(row.repObject);
  1925.             if (category)
  1926.                 setClass(netInfoBox, "category-" + category);
  1927.             row.setAttribute('aria-expanded', 'true');
  1928.         }
  1929.         else
  1930.         {
  1931.             var netInfoRow = row.nextSibling;
  1932.             var netInfoBox = netInfoRow.getElementsByClassName("netInfoBody").item(0);
  1933.  
  1934.             dispatch(NetInfoBody.fbListeners, "destroyTabBody", [netInfoBox, file]);
  1935.  
  1936.             row.parentNode.removeChild(netInfoRow);
  1937.             row.setAttribute('aria-expanded', 'false');
  1938.         }
  1939.     },
  1940.  
  1941.     getCategory: function(file)
  1942.     {
  1943.         var category = Utils.getFileCategory(file);
  1944.         if (category)
  1945.             return "category-" + category;
  1946.  
  1947.         return "category-undefined";
  1948.     },
  1949.  
  1950.     getInFrame: function(file)
  1951.     {
  1952.         return !!file.document.parent;
  1953.     },
  1954.  
  1955.     getIndent: function(file)
  1956.     {
  1957.         // XXXjoe Turn off indenting for now, it's confusing since we don't
  1958.         // actually place nested files directly below their parent
  1959.         //return file.document.level * indentWidth;
  1960.         return 10;
  1961.     },
  1962.  
  1963.     isError: function(file)
  1964.     {
  1965.         if (file.aborted)
  1966.             return true;
  1967.  
  1968.         var errorRange = Math.floor(file.responseStatus/100);
  1969.         return errorRange == 4 || errorRange == 5;
  1970.     },
  1971.  
  1972.     getHref: function(file)
  1973.     {
  1974.         return (file.method ? file.method.toUpperCase() : "?") + " " + getFileName(file.href);
  1975.     },
  1976.  
  1977.     getStatus: function(file)
  1978.     {
  1979.         var text = "";
  1980.  
  1981.         if (file.responseStatus)
  1982.             text += file.responseStatus + " ";
  1983.  
  1984.         if (file.responseStatusText)
  1985.             text += file.responseStatusText;
  1986.  
  1987.         return text ? text : " ";
  1988.     },
  1989.  
  1990.     getDomain: function(file)
  1991.     {
  1992.         return getPrettyDomain(file.href);
  1993.     },
  1994.  
  1995.     getSize: function(file)
  1996.     {
  1997.         return this.formatSize(file.size);
  1998.     },
  1999.  
  2000.     getElapsedTime: function(file)
  2001.     {
  2002.         if (!file.elapsed || file.elapsed < 0)
  2003.             return "";
  2004.  
  2005.         return this.formatTime(file.elapsed);
  2006.     },
  2007.  
  2008.     hasResponseHeaders: function(file)
  2009.     {
  2010.         return !!file.responseHeaders;
  2011.     },
  2012.  
  2013.     formatSize: function(bytes)
  2014.     {
  2015.         return formatSize(bytes);
  2016.     },
  2017.  
  2018.     formatTime: function(elapsed)
  2019.     {
  2020.         // Use formatTime util from the lib.
  2021.         return formatTime(elapsed);
  2022.     }
  2023. });
  2024.  
  2025. var NetRequestEntry = Firebug.NetMonitor.NetRequestEntry;
  2026.  
  2027. // ************************************************************************************************
  2028.  
  2029. Firebug.NetMonitor.NetPage = domplate(Firebug.Rep,
  2030. {
  2031.     separatorTag:
  2032.         TR({"class": "netRow netPageSeparatorRow"},
  2033.             TD({"class": "netCol netPageSeparatorLabel", colspan: 6, "role": "separator"})
  2034.         ),
  2035.  
  2036.     pageTag:
  2037.         TR({"class": "netRow netPageRow", onclick: "$onPageClick"},
  2038.             TD({"class": "netCol netPageCol", colspan: 6, "role": "separator"},
  2039.                 DIV({"class": "netLabel netPageLabel netPageTitle"}, "$page|getTitle")
  2040.             )
  2041.         ),
  2042.  
  2043.     getTitle: function(page)
  2044.     {
  2045.         return page.pageTitle;
  2046.     },
  2047.  
  2048.     onPageClick: function(event)
  2049.     {
  2050.         if (!isLeftClick(event))
  2051.             return;
  2052.  
  2053.         var target = event.target;
  2054.         var pageRow = getAncestorByClass(event.target, "netPageRow");
  2055.         var panel = Firebug.getElementPanel(pageRow);
  2056.  
  2057.         if (!hasClass(pageRow, "opened"))
  2058.         {
  2059.             setClass(pageRow, "opened");
  2060.  
  2061.             var files = pageRow.files;
  2062.  
  2063.             // Move all net-rows from the persistedState to this panel.
  2064.             panel.insertRows(files, pageRow);
  2065.  
  2066.             for (var i=0; i<files.length; i++)
  2067.                 panel.queue.push(files[i].file);
  2068.  
  2069.             panel.layout();
  2070.         }
  2071.         else
  2072.         {
  2073.             removeClass(pageRow, "opened");
  2074.  
  2075.             var nextRow = pageRow.nextSibling;
  2076.             while (!hasClass(nextRow, "netPageRow") && !hasClass(nextRow, "netPageSeparatorRow"))
  2077.             {
  2078.                 var nextSibling = nextRow.nextSibling;
  2079.                 nextRow.parentNode.removeChild(nextRow);
  2080.                 nextRow = nextSibling;
  2081.             }
  2082.         }
  2083.     },
  2084. });
  2085.  
  2086. var NetPage = Firebug.NetMonitor.NetPage;
  2087.  
  2088. // ************************************************************************************************
  2089.  
  2090. /**
  2091.  * @domplate Represents a template that is used to reneder detailed info about a request.
  2092.  * This template is rendered when a request is expanded.
  2093.  */
  2094. Firebug.NetMonitor.NetInfoBody = domplate(Firebug.Rep, new Firebug.Listener(),
  2095. {
  2096.     tag:
  2097.         DIV({"class": "netInfoBody", _repObject: "$file"},
  2098.             TAG("$infoTabs", {file: "$file"}),
  2099.             TAG("$infoBodies", {file: "$file"})
  2100.         ),
  2101.  
  2102.     infoTabs:
  2103.         DIV({"class": "netInfoTabs focusRow subFocusRow", "role": "tablist"},
  2104.             A({"class": "netInfoParamsTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab",
  2105.                 view: "Params",
  2106.                 $collapsed: "$file|hideParams"},
  2107.                 $STR("URLParameters")
  2108.             ),
  2109.             A({"class": "netInfoHeadersTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab",
  2110.                 view: "Headers"},
  2111.                 $STR("Headers")
  2112.             ),
  2113.             A({"class": "netInfoPostTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab",
  2114.                 view: "Post",
  2115.                 $collapsed: "$file|hidePost"},
  2116.                 $STR("Post")
  2117.             ),
  2118.             A({"class": "netInfoPutTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab",
  2119.                 view: "Put",
  2120.                 $collapsed: "$file|hidePut"},
  2121.                 $STR("Put")
  2122.             ),
  2123.             A({"class": "netInfoResponseTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab",
  2124.                 view: "Response",
  2125.                 $collapsed: "$file|hideResponse"},
  2126.                 $STR("Response")
  2127.             ),
  2128.             A({"class": "netInfoCacheTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab",
  2129.                view: "Cache",
  2130.                $collapsed: "$file|hideCache"},
  2131.                $STR("Cache")
  2132.             ),
  2133.             A({"class": "netInfoHtmlTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab",
  2134.                view: "Html",
  2135.                $collapsed: "$file|hideHtml"},
  2136.                $STR("HTML")
  2137.             )
  2138.         ),
  2139.  
  2140.     infoBodies:
  2141.         DIV({"class": "netInfoBodies outerFocusRow"},
  2142.             TABLE({"class": "netInfoParamsText netInfoText netInfoParamsTable", "role": "tabpanel",
  2143.                     cellpadding: 0, cellspacing: 0}, TBODY()),
  2144.             DIV({"class": "netInfoHeadersText netInfoText", "role": "tabpanel"}),
  2145.             DIV({"class": "netInfoPostText netInfoText", "role": "tabpanel"}),
  2146.             DIV({"class": "netInfoPutText netInfoText", "role": "tabpanel"}),
  2147.             DIV({"class": "netInfoResponseText netInfoText", "role": "tabpanel"}),
  2148.             DIV({"class": "netInfoCacheText netInfoText", "role": "tabpanel"},
  2149.                 TABLE({"class": "netInfoCacheTable", cellpadding: 0, cellspacing: 0, "role": "presentation"},
  2150.                     TBODY({"role": "list", "aria-label": $STR("Cache")})
  2151.                 )
  2152.             ),
  2153.             DIV({"class": "netInfoHtmlText netInfoText", "role": "tabpanel"},
  2154.                 IFRAME({"class": "netInfoHtmlPreview", "role": "document"})
  2155.             )
  2156.         ),
  2157.  
  2158.     headerDataTag:
  2159.         FOR("param", "$headers",
  2160.             TR({"role": "listitem"},
  2161.                 TD({"class": "netInfoParamName", "role": "presentation"},
  2162.                     TAG("$param|getNameTag", {param: "$param"})
  2163.                 ),
  2164.                 TD({"class": "netInfoParamValue", "role": "list", "aria-label": "$param.name"},
  2165.                     FOR("line", "$param|getParamValueIterator",
  2166.                         CODE({"class": "focusRow subFocusRow", "role": "listitem"}, "$line")
  2167.                     )
  2168.                 )
  2169.             )
  2170.         ),
  2171.  
  2172.     customTab:
  2173.         A({"class": "netInfo$tabId\\Tab netInfoTab", onclick: "$onClickTab", view: "$tabId", "role": "tab"},
  2174.             "$tabTitle"
  2175.         ),
  2176.  
  2177.     customBody:
  2178.         DIV({"class": "netInfo$tabId\\Text netInfoText", "role": "tabpanel"}),
  2179.  
  2180.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  2181.  
  2182.     nameTag:
  2183.         SPAN("$param|getParamName"),
  2184.  
  2185.     nameWithTooltipTag:
  2186.         SPAN({title: "$param.name"}, "$param|getParamName"),
  2187.  
  2188.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  2189.  
  2190.     getNameTag: function(param)
  2191.     {
  2192.         return (this.getParamName(param) == param.name) ? this.nameTag : this.nameWithTooltipTag;
  2193.     },
  2194.  
  2195.     getParamName: function(param)
  2196.     {
  2197.         var limit = 25;
  2198.         var name = param.name;
  2199.         if (name.length > limit)
  2200.             name = name.substr(0, limit) + "...";
  2201.         return name;
  2202.     },
  2203.  
  2204.     getParamTitle: function(param)
  2205.     {
  2206.         var limit = 25;
  2207.         var name = param.name;
  2208.         if (name.length > limit)
  2209.             return name;
  2210.         return "";
  2211.     },
  2212.  
  2213.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  2214.  
  2215.     hideParams: function(file)
  2216.     {
  2217.         return !file.urlParams || !file.urlParams.length;
  2218.     },
  2219.  
  2220.     hidePost: function(file)
  2221.     {
  2222.         return file.method.toUpperCase() != "POST";
  2223.     },
  2224.  
  2225.     hidePut: function(file)
  2226.     {
  2227.         return file.method.toUpperCase() != "PUT";
  2228.     },
  2229.  
  2230.     hideResponse: function(file)
  2231.     {
  2232.         return file.category in binaryFileCategories;
  2233.     },
  2234.  
  2235.     hideCache: function(file)
  2236.     {
  2237.         //xxxHonza: I don't see any reason why not to display the cache also info for images.
  2238.         return !file.cacheEntry/* || file.category=="image"*/;
  2239.     },
  2240.  
  2241.     hideHtml: function(file)
  2242.     {
  2243.         return (file.mimeType != "text/html") && (file.mimeType != "application/xhtml+xml");
  2244.     },
  2245.  
  2246.     onClickTab: function(event)
  2247.     {
  2248.         this.selectTab(event.currentTarget);
  2249.     },
  2250.  
  2251.     getParamValueIterator: function(param)
  2252.     {
  2253.         // This value is inserted into CODE element and so, make sure the HTML isn't escaped (1210).
  2254.         // This is why the second parameter is true.
  2255.         // The CODE (with style white-space:pre) element preserves whitespaces so they are
  2256.         // displayed the same, as they come from the server (1194).
  2257.         // In case of a long header values of post parameters the value must be wrapped (2105).
  2258.         return wrapText(param.value, true);
  2259.     },
  2260.  
  2261.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  2262.  
  2263.     appendTab: function(netInfoBox, tabId, tabTitle)
  2264.     {
  2265.         // Create new tab and body.
  2266.         var args = {tabId: tabId, tabTitle: tabTitle};
  2267.         this.customTab.append(args, netInfoBox.getElementsByClassName("netInfoTabs").item(0));
  2268.         this.customBody.append(args, netInfoBox.getElementsByClassName("netInfoBodies").item(0));
  2269.     },
  2270.  
  2271.     selectTabByName: function(netInfoBox, tabName)
  2272.     {
  2273.         var tab = getChildByClass(netInfoBox, "netInfoTabs", "netInfo"+tabName+"Tab");
  2274.         if (tab)
  2275.             this.selectTab(tab);
  2276.     },
  2277.  
  2278.     selectTab: function(tab)
  2279.     {
  2280.         var netInfoBox = getAncestorByClass(tab, "netInfoBody");
  2281.  
  2282.         var view = tab.getAttribute("view");
  2283.         if (netInfoBox.selectedTab)
  2284.         {
  2285.             netInfoBox.selectedTab.removeAttribute("selected");
  2286.             netInfoBox.selectedText.removeAttribute("selected");
  2287.             netInfoBox.selectedTab.setAttribute("aria-selected", "false");
  2288.         }
  2289.  
  2290.         var textBodyName = "netInfo" + view + "Text";
  2291.  
  2292.         netInfoBox.selectedTab = tab;
  2293.         netInfoBox.selectedText = netInfoBox.getElementsByClassName(textBodyName).item(0);
  2294.  
  2295.         netInfoBox.selectedTab.setAttribute("selected", "true");
  2296.         netInfoBox.selectedText.setAttribute("selected", "true");
  2297.         netInfoBox.selectedTab.setAttribute("aria-selected", "true");
  2298.  
  2299.         var file = Firebug.getRepObject(netInfoBox);
  2300.         var context = Firebug.getElementPanel(netInfoBox).context;
  2301.         this.updateInfo(netInfoBox, file, context);
  2302.     },
  2303.  
  2304.     updateInfo: function(netInfoBox, file, context)
  2305.     {
  2306.         if (!netInfoBox)
  2307.         {
  2308.             return;
  2309.         }
  2310.  
  2311.         var tab = netInfoBox.selectedTab;
  2312.         if (hasClass(tab, "netInfoParamsTab"))
  2313.         {
  2314.             if (file.urlParams && !netInfoBox.urlParamsPresented)
  2315.             {
  2316.                 netInfoBox.urlParamsPresented = true;
  2317.                 this.insertHeaderRows(netInfoBox, file.urlParams, "Params");
  2318.             }
  2319.         }
  2320.  
  2321.         if (hasClass(tab, "netInfoHeadersTab"))
  2322.         {
  2323.             var headersText = netInfoBox.getElementsByClassName("netInfoHeadersText").item(0);
  2324.  
  2325.             if (file.responseHeaders && !netInfoBox.responseHeadersPresented)
  2326.             {
  2327.                 netInfoBox.responseHeadersPresented = true;
  2328.                 NetInfoHeaders.renderHeaders(headersText, file.responseHeaders, "ResponseHeaders");
  2329.             }
  2330.  
  2331.             if (file.requestHeaders && !netInfoBox.requestHeadersPresented)
  2332.             {
  2333.                 netInfoBox.requestHeadersPresented = true;
  2334.                 NetInfoHeaders.renderHeaders(headersText, file.requestHeaders, "RequestHeaders");
  2335.             }
  2336.         }
  2337.  
  2338.         if (hasClass(tab, "netInfoPostTab"))
  2339.         {
  2340.             if (!netInfoBox.postPresented)
  2341.             {
  2342.                 netInfoBox.postPresented  = true;
  2343.                 var postText = netInfoBox.getElementsByClassName("netInfoPostText").item(0);
  2344.                 NetInfoPostData.render(context, postText, file);
  2345.             }
  2346.         }
  2347.  
  2348.         if (hasClass(tab, "netInfoPutTab"))
  2349.         {
  2350.             if (!netInfoBox.putPresented)
  2351.             {
  2352.                 netInfoBox.putPresented  = true;
  2353.                 var putText = netInfoBox.getElementsByClassName("netInfoPutText").item(0);
  2354.                 NetInfoPostData.render(context, putText, file);
  2355.             }
  2356.         }
  2357.  
  2358.         if (hasClass(tab, "netInfoResponseTab") && file.loaded && !netInfoBox.responsePresented)
  2359.         {
  2360.             var responseTextBox = netInfoBox.getElementsByClassName("netInfoResponseText").item(0);
  2361.             if (file.category == "image")
  2362.             {
  2363.                 netInfoBox.responsePresented = true;
  2364.  
  2365.                 var responseImage = netInfoBox.ownerDocument.createElement("img");
  2366.                 responseImage.src = file.href;
  2367.  
  2368.                 clearNode(responseTextBox);
  2369.                 responseTextBox.appendChild(responseImage, responseTextBox);
  2370.             }
  2371.             else if (!(binaryCategoryMap.hasOwnProperty(file.category)))
  2372.             {
  2373.                 this.setResponseText(file, netInfoBox, responseTextBox, context);
  2374.             }
  2375.         }
  2376.  
  2377.         if (hasClass(tab, "netInfoCacheTab") && file.loaded && !netInfoBox.cachePresented)
  2378.         {
  2379.             var responseTextBox = netInfoBox.getElementsByClassName("netInfoCacheText").item(0);
  2380.             if (file.cacheEntry) {
  2381.                 netInfoBox.cachePresented = true;
  2382.                 this.insertHeaderRows(netInfoBox, file.cacheEntry, "Cache");
  2383.             }
  2384.         }
  2385.  
  2386.         if (hasClass(tab, "netInfoHtmlTab") && file.loaded && !netInfoBox.htmlPresented)
  2387.         {
  2388.             netInfoBox.htmlPresented = true;
  2389.  
  2390.             var text = Utils.getResponseText(file, context);
  2391.             var iframe = netInfoBox.getElementsByClassName("netInfoHtmlPreview").item(0);
  2392.             iframe.contentWindow.document.body.innerHTML = text;
  2393.         }
  2394.  
  2395.         // Notify listeners about update so, content of custom tabs can be updated.
  2396.         dispatch(NetInfoBody.fbListeners, "updateTabBody", [netInfoBox, file, context]);
  2397.     },
  2398.  
  2399.     setResponseText: function(file, netInfoBox, responseTextBox, context)
  2400.     {
  2401.         // Get response text and make sure it doesn't exceed the max limit.
  2402.         var text = Utils.getResponseText(file, context);
  2403.         var limit = Firebug.netDisplayedResponseLimit + 15;
  2404.         var limitReached = text ? (text.length > limit) : false;
  2405.         if (limitReached)
  2406.             text = text.substr(0, limit) + "...";
  2407.  
  2408.         // Insert the response into the UI.
  2409.         if (text)
  2410.             insertWrappedText(text, responseTextBox);
  2411.         else
  2412.             insertWrappedText("", responseTextBox);
  2413.  
  2414.         // Append a message informing the user that the response isn't fully displayed.
  2415.         if (limitReached)
  2416.         {
  2417.             var object = {
  2418.                 text: $STR("net.responseSizeLimitMessage"),
  2419.                 onClickLink: function() {
  2420.                     var panel = context.getPanel("net", true);
  2421.                     panel.openResponseInTab(file);
  2422.                 }
  2423.             };
  2424.             Firebug.NetMonitor.ResponseSizeLimit.append(object, responseTextBox);
  2425.         }
  2426.  
  2427.         netInfoBox.responsePresented = true;
  2428.  
  2429.     },
  2430.  
  2431.     insertHeaderRows: function(netInfoBox, headers, tableName, rowName)
  2432.     {
  2433.         if (!headers.length)
  2434.             return;
  2435.  
  2436.         var headersTable = netInfoBox.getElementsByClassName("netInfo"+tableName+"Table").item(0);
  2437.         var tbody = getChildByClass(headersTable, "netInfo" + rowName + "Body");
  2438.         if (!tbody)
  2439.             tbody = headersTable.firstChild;
  2440.         var titleRow = getChildByClass(tbody, "netInfo" + rowName + "Title");
  2441.  
  2442.         this.headerDataTag.insertRows({headers: headers}, titleRow ? titleRow : tbody);
  2443.         removeClass(titleRow, "collapsed");
  2444.     },
  2445. });
  2446.  
  2447. var NetInfoBody = Firebug.NetMonitor.NetInfoBody;
  2448.  
  2449. // ************************************************************************************************
  2450.  
  2451. /**
  2452.  * @domplate Represents posted data within request info (the info, which is visible when
  2453.  * a request entry is expanded. This template renders content of the Post tab.
  2454.  */
  2455. Firebug.NetMonitor.NetInfoPostData = domplate(Firebug.Rep, new Firebug.Listener(),
  2456. {
  2457.     // application/x-www-form-urlencoded
  2458.     paramsTable:
  2459.         TABLE({"class": "netInfoPostParamsTable", cellpadding: 0, cellspacing: 0, "role": "presentation"},
  2460.             TBODY({"role": "list", "aria-label": $STR("net.label.Parameters")},
  2461.                 TR({"class": "netInfoPostParamsTitle", "role": "presentation"},
  2462.                     TD({colspan: 2, "role": "presentation"},
  2463.                         DIV({"class": "netInfoPostParams"},
  2464.                             $STR("net.label.Parameters"),
  2465.                             SPAN({"class": "netInfoPostContentType"},
  2466.                                 "application/x-www-form-urlencoded"
  2467.                             )
  2468.                         )
  2469.                     )
  2470.                 )
  2471.             )
  2472.         ),
  2473.  
  2474.     // multipart/form-data
  2475.     partsTable:
  2476.         TABLE({"class": "netInfoPostPartsTable", cellpadding: 0, cellspacing: 0, "role": "presentation"},
  2477.             TBODY({"role": "list", "aria-label": $STR("net.label.Parts")},
  2478.                 TR({"class": "netInfoPostPartsTitle", "role": "presentation"},
  2479.                     TD({colspan: 2, "role":"presentation" },
  2480.                         DIV({"class": "netInfoPostParams"},
  2481.                             $STR("net.label.Parts"),
  2482.                             SPAN({"class": "netInfoPostContentType"},
  2483.                                 "multipart/form-data"
  2484.                             )
  2485.                         )
  2486.                     )
  2487.                 )
  2488.             )
  2489.         ),
  2490.  
  2491.     // application/json
  2492.     jsonTable:
  2493.         TABLE({"class": "netInfoPostJSONTable", cellpadding: 0, cellspacing: 0, "role": "presentation"},
  2494.             TBODY({"role": "list", "aria-label": $STR("jsonviewer.tab.JSON")},
  2495.                 TR({"class": "netInfoPostJSONTitle", "role": "presentation"},
  2496.                     TD({"role": "presentation" },
  2497.                         DIV({"class": "netInfoPostParams"},
  2498.                             $STR("jsonviewer.tab.JSON")
  2499.                         )
  2500.                     )
  2501.                 ),
  2502.                 TR(
  2503.                     TD({"class": "netInfoPostJSONBody"})
  2504.                 )
  2505.             )
  2506.         ),
  2507.  
  2508.     // application/xml
  2509.     xmlTable:
  2510.         TABLE({"class": "netInfoPostXMLTable", cellpadding: 0, cellspacing: 0, "role": "presentation"},
  2511.             TBODY({"role": "list", "aria-label": $STR("xmlviewer.tab.XML")},
  2512.                 TR({"class": "netInfoPostXMLTitle", "role": "presentation"},
  2513.                     TD({"role": "presentation" },
  2514.                         DIV({"class": "netInfoPostParams"},
  2515.                             $STR("xmlviewer.tab.XML")
  2516.                         )
  2517.                     )
  2518.                 ),
  2519.                 TR(
  2520.                     TD({"class": "netInfoPostXMLBody"})
  2521.                 )
  2522.             )
  2523.         ),
  2524.  
  2525.     sourceTable:
  2526.         TABLE({"class": "netInfoPostSourceTable", cellpadding: 0, cellspacing: 0, "role": "presentation"},
  2527.             TBODY({"role": "list", "aria-label": $STR("net.label.Source")},
  2528.                 TR({"class": "netInfoPostSourceTitle", "role": "presentation"},
  2529.                     TD({colspan: 2, "role": "presentation"},
  2530.                         DIV({"class": "netInfoPostSource"},
  2531.                             $STR("net.label.Source")
  2532.                         )
  2533.                     )
  2534.                 )
  2535.             )
  2536.         ),
  2537.  
  2538.     sourceBodyTag:
  2539.         TR({"role": "presentation"},
  2540.             TD({colspan: 2, "role": "presentation"},
  2541.                 FOR("line", "$param|getParamValueIterator",
  2542.                     CODE({"class":"focusRow subFocusRow" , "role": "listitem"},"$line")
  2543.                 )
  2544.             )
  2545.         ),
  2546.  
  2547.     getParamValueIterator: function(param)
  2548.     {
  2549.         return NetInfoBody.getParamValueIterator(param);
  2550.     },
  2551.  
  2552.     render: function(context, parentNode, file)
  2553.     {
  2554.         var text = Utils.getPostText(file, context, true);
  2555.         if (text == undefined)
  2556.             return;
  2557.  
  2558.         if (Utils.isURLEncodedRequest(file, context))
  2559.         {
  2560.             var lines = text.split("\n");
  2561.             var params = parseURLEncodedText(lines[lines.length-1]);
  2562.             if (params)
  2563.                 this.insertParameters(parentNode, params);
  2564.         }
  2565.  
  2566.         if (Utils.isMultiPartRequest(file, context))
  2567.         {
  2568.             var data = this.parseMultiPartText(file, context);
  2569.             if (data)
  2570.                 this.insertParts(parentNode, data);
  2571.         }
  2572.  
  2573.         var contentType = Utils.findHeader(file.requestHeaders, "content-type");
  2574.  
  2575.         if (Firebug.JSONViewerModel.isJSON(contentType))
  2576.             this.insertJSON(parentNode, file, context);
  2577.  
  2578.         if (Firebug.XMLViewerModel.isXML(contentType))
  2579.             this.insertXML(parentNode, file, context);
  2580.  
  2581.         var postText = Utils.getPostText(file, context);
  2582.         postText = Utils.formatPostText(postText);
  2583.         if (postText)
  2584.             this.insertSource(parentNode, postText);
  2585.     },
  2586.  
  2587.     insertParameters: function(parentNode, params)
  2588.     {
  2589.         if (!params || !params.length)
  2590.             return;
  2591.  
  2592.         var paramTable = this.paramsTable.append(null, parentNode);
  2593.         var row = paramTable.getElementsByClassName("netInfoPostParamsTitle").item(0);
  2594.  
  2595.         NetInfoBody.headerDataTag.insertRows({headers: params}, row);
  2596.     },
  2597.  
  2598.     insertParts: function(parentNode, data)
  2599.     {
  2600.         if (!data.params || !data.params.length)
  2601.             return;
  2602.  
  2603.         var partsTable = this.partsTable.append(null, parentNode);
  2604.         var row = partsTable.getElementsByClassName("netInfoPostPartsTitle").item(0);
  2605.  
  2606.         NetInfoBody.headerDataTag.insertRows({headers: data.params}, row);
  2607.     },
  2608.  
  2609.     insertJSON: function(parentNode, file, context)
  2610.     {
  2611.         var text = Utils.getPostText(file, context);
  2612.         var data = parseJSONString(text, "http://" + file.request.originalURI.host);
  2613.         if (!data)
  2614.             return;
  2615.  
  2616.         var jsonTable = this.jsonTable.append(null, parentNode);
  2617.         var jsonBody = jsonTable.getElementsByClassName("netInfoPostJSONBody").item(0);
  2618.  
  2619.         if (!this.toggles)
  2620.             this.toggles = {};
  2621.  
  2622.         Firebug.DOMPanel.DirTable.tag.replace(
  2623.             {object: data, toggles: this.toggles}, jsonBody);
  2624.     },
  2625.  
  2626.     insertXML: function(parentNode, file, context)
  2627.     {
  2628.         var text = Utils.getPostText(file, context);
  2629.  
  2630.         var jsonTable = this.xmlTable.append(null, parentNode);
  2631.         var jsonBody = jsonTable.getElementsByClassName("netInfoPostXMLBody").item(0);
  2632.  
  2633.         Firebug.XMLViewerModel.insertXML(jsonBody, text);
  2634.     },
  2635.  
  2636.     insertSource: function(parentNode, text)
  2637.     {
  2638.         var sourceTable = this.sourceTable.append(null, parentNode);
  2639.         var row = sourceTable.getElementsByClassName("netInfoPostSourceTitle").item(0);
  2640.  
  2641.         var param = {value: text};
  2642.         this.sourceBodyTag.insertRows({param: param}, row);
  2643.     },
  2644.  
  2645.     parseMultiPartText: function(file, context)
  2646.     {
  2647.         var text = Utils.getPostText(file, context);
  2648.         if (text == undefined)
  2649.             return null;
  2650.  
  2651.         FBTrace.sysout("net.parseMultiPartText; boundary: ", text);
  2652.  
  2653.         var boundary = text.match(/\s*boundary=\s*(.*)/)[1];
  2654.  
  2655.         var divider = "\r\n\r\n";
  2656.         var bodyStart = text.indexOf(divider);
  2657.         var body = text.substr(bodyStart + divider.length);
  2658.  
  2659.         var postData = {};
  2660.         postData.mimeType = "multipart/form-data";
  2661.         postData.params = [];
  2662.  
  2663.         var parts = body.split("--" + boundary);
  2664.         for (var i=0; i<parts.length; i++)
  2665.         {
  2666.             var part = parts[i].split(divider);
  2667.             if (part.length != 2)
  2668.                 continue;
  2669.  
  2670.             var m = part[0].match(/\s*name=\"(.*)\"(;|$)/);
  2671.             postData.params.push({
  2672.                 name: (m && m.length > 1) ? m[1] : "",
  2673.                 value: trim(part[1])
  2674.             })
  2675.         }
  2676.  
  2677.         return postData;
  2678.     }
  2679. });
  2680.  
  2681. var NetInfoPostData = Firebug.NetMonitor.NetInfoPostData;
  2682.  
  2683. // ************************************************************************************************
  2684.  
  2685. /**
  2686.  * @domplate Used within the Net panel to display raw source of request and response headers
  2687.  * as well as pretty-formatted summary of these headers.
  2688.  */
  2689. Firebug.NetMonitor.NetInfoHeaders = domplate(Firebug.Rep, new Firebug.Listener(),
  2690. {
  2691.     tag:
  2692.         DIV({"class": "netInfoHeadersTable", "role": "tabpanel"},
  2693.             DIV({"class": "netInfoHeadersGroup netInfoResponseHeadersTitle"},
  2694.                 SPAN($STR("ResponseHeaders")),
  2695.                 SPAN({"class": "netHeadersViewSource response collapsed", onclick: "$onViewSource",
  2696.                     _sourceDisplayed: false, _rowName: "ResponseHeaders"},
  2697.                     $STR("net.headers.view source")
  2698.                 )
  2699.             ),
  2700.             TABLE({cellpadding: 0, cellspacing: 0},
  2701.                 TBODY({"class": "netInfoResponseHeadersBody", "role": "list",
  2702.                     "aria-label": $STR("ResponseHeaders")})
  2703.             ),
  2704.             DIV({"class": "netInfoHeadersGroup netInfoRequestHeadersTitle"},
  2705.                 SPAN($STR("RequestHeaders")),
  2706.                 SPAN({"class": "netHeadersViewSource request collapsed", onclick: "$onViewSource",
  2707.                     _sourceDisplayed: false, _rowName: "RequestHeaders"},
  2708.                     $STR("net.headers.view source")
  2709.                 )
  2710.             ),
  2711.             TABLE({cellpadding: 0, cellspacing: 0},
  2712.                 TBODY({"class": "netInfoRequestHeadersBody", "role": "list",
  2713.                     "aria-label": $STR("RequestHeaders")})
  2714.             )
  2715.         ),
  2716.  
  2717.     sourceTag:
  2718.         TR({"role": "presentation"},
  2719.             TD({colspan: 2, "role": "presentation"},
  2720.                 PRE({"class": "source"})
  2721.             )
  2722.         ),
  2723.  
  2724.     onViewSource: function(event)
  2725.     {
  2726.         var target = event.target;
  2727.         var requestHeaders = (target.rowName == "RequestHeaders");
  2728.  
  2729.         var netInfoBox = getAncestorByClass(target, "netInfoBody");
  2730.         var file = netInfoBox.repObject;
  2731.  
  2732.         if (target.sourceDisplayed)
  2733.         {
  2734.             var headers = requestHeaders ? file.requestHeaders : file.responseHeaders;
  2735.             this.insertHeaderRows(netInfoBox, headers, target.rowName);
  2736.             target.innerHTML = $STR("net.headers.view source");
  2737.         }
  2738.         else
  2739.         {
  2740.             var source = requestHeaders ? file.requestHeadersText : file.responseHeadersText;
  2741.             this.insertSource(netInfoBox, source, target.rowName);
  2742.             target.innerHTML = $STR("net.headers.pretty print");
  2743.         }
  2744.  
  2745.         target.sourceDisplayed = !target.sourceDisplayed;
  2746.  
  2747.         cancelEvent(event);
  2748.     },
  2749.  
  2750.     insertSource: function(netInfoBox, source, rowName)
  2751.     {
  2752.         // This breaks copy to clipboard.
  2753.         //if (source)
  2754.         //    source = source.replace(/\r\n/gm, "<span style='color:lightgray'>\\r\\n</span>\r\n");
  2755.  
  2756.         var tbody = netInfoBox.getElementsByClassName("netInfo" + rowName + "Body").item(0);
  2757.         var node = this.sourceTag.replace({}, tbody);
  2758.         var sourceNode = node.getElementsByClassName("source").item(0);
  2759.         sourceNode.innerHTML = source;
  2760.     },
  2761.  
  2762.     insertHeaderRows: function(netInfoBox, headers, rowName)
  2763.     {
  2764.         var headersTable = netInfoBox.getElementsByClassName("netInfoHeadersTable").item(0);
  2765.         var tbody = headersTable.getElementsByClassName("netInfo" + rowName + "Body").item(0);
  2766.  
  2767.         clearNode(tbody);
  2768.  
  2769.         if (!headers.length)
  2770.             return;
  2771.  
  2772.         NetInfoBody.headerDataTag.insertRows({headers: headers}, tbody);
  2773.  
  2774.         var titleRow = getChildByClass(headersTable, "netInfo" + rowName + "Title");
  2775.         removeClass(titleRow, "collapsed");
  2776.     },
  2777.  
  2778.     init: function(parent)
  2779.     {
  2780.         var rootNode = this.tag.append({}, parent);
  2781.  
  2782.         var netInfoBox = getAncestorByClass(parent, "netInfoBody");
  2783.         var file = netInfoBox.repObject;
  2784.  
  2785.         var viewSource;
  2786.  
  2787.         viewSource = rootNode.getElementsByClassName("netHeadersViewSource request").item(0);
  2788.         if (file.requestHeadersText)
  2789.             removeClass(viewSource, "collapsed");
  2790.  
  2791.         viewSource = rootNode.getElementsByClassName("netHeadersViewSource response").item(0);
  2792.         if (file.responseHeadersText)
  2793.             removeClass(viewSource, "collapsed");
  2794.     },
  2795.  
  2796.     renderHeaders: function(parent, headers, rowName)
  2797.     {
  2798.         if (!parent.firstChild)
  2799.             this.init(parent);
  2800.  
  2801.         this.insertHeaderRows(parent, headers, rowName);
  2802.     }
  2803. });
  2804.  
  2805. var NetInfoHeaders = Firebug.NetMonitor.NetInfoHeaders;
  2806.  
  2807. // ************************************************************************************************
  2808.  
  2809. /**
  2810.  * @domplate Represents a template for popup tip that displays detailed timing info about
  2811.  * a network request.
  2812.  */
  2813. Firebug.NetMonitor.TimeInfoTip = domplate(Firebug.Rep,
  2814. {
  2815.     tableTag:
  2816.         TABLE({"class": "timeInfoTip", "id": "fbNetTimeInfoTip"},
  2817.             TBODY()
  2818.         ),
  2819.  
  2820.     timingsTag:
  2821.         FOR("time", "$timings",
  2822.             TR({"class": "timeInfoTipRow", $collapsed: "$time|hideBar"},
  2823.                 TD({"class": "$time|getBarClass timeInfoTipBar",
  2824.                     $loaded: "$time.loaded",
  2825.                     $fromCache: "$time.fromCache",
  2826.                 }),
  2827.                 TD({"class": "timeInfoTipCell startTime"},
  2828.                     "$time.start|formatStartTime"
  2829.                 ),
  2830.                 TD({"class": "timeInfoTipCell elapsedTime"},
  2831.                     "$time.elapsed|formatTime"
  2832.                 ),
  2833.                 TD("$time|getLabel")
  2834.             )
  2835.         ),
  2836.  
  2837.     startTimeTag:
  2838.         TR(
  2839.             TD(),
  2840.             TD("$startTime.time|formatStartTime"),
  2841.             TD({"colspan": 2},
  2842.                 "$startTime|getLabel"
  2843.             )
  2844.         ),
  2845.  
  2846.     separatorTag:
  2847.         TR(
  2848.             TD({"colspan": 4, "height": "10px"})
  2849.         ),
  2850.  
  2851.     eventsTag:
  2852.         FOR("event", "$events",
  2853.             TR({"class": "timeInfoTipEventRow"},
  2854.                 TD({"class": "timeInfoTipBar", align: "center"},
  2855.                     DIV({"class": "$event|getBarClass timeInfoTipEventBar"})
  2856.                 ),
  2857.                 TD("$event.start|formatStartTime"),
  2858.                 TD({"colspan": 2},
  2859.                     "$event|getLabel"
  2860.                 )
  2861.             )
  2862.         ),
  2863.  
  2864.     hideBar: function(obj)
  2865.     {
  2866.         return !obj.elapsed && obj.bar == "Blocking";
  2867.     },
  2868.  
  2869.     getBarClass: function(obj)
  2870.     {
  2871.         return "net" + obj.bar + "Bar";
  2872.     },
  2873.  
  2874.     formatTime: function(time)
  2875.     {
  2876.         return formatTime(time)
  2877.     },
  2878.  
  2879.     formatStartTime: function(time)
  2880.     {
  2881.         var label = formatTime(time);
  2882.         if (!time)
  2883.             return label;
  2884.  
  2885.         return (time > 0 ? "+" : "") + label;
  2886.     },
  2887.  
  2888.     getLabel: function(obj)
  2889.     {
  2890.         return $STR("requestinfo." + obj.bar);
  2891.     },
  2892.  
  2893.     render: function(file, parentNode)
  2894.     {
  2895.         var infoTip = Firebug.NetMonitor.TimeInfoTip.tableTag.replace({}, parentNode);
  2896.  
  2897.         var elapsed = file.loaded ? file.endTime - file.startTime : file.phase.phaseEndTime - file.startTime;
  2898.         var blockingEnd = (file.sendingTime > file.startTime) ? file.sendingTime : file.waitingForTime;
  2899.  
  2900.         var timings = [];
  2901.         timings.push({bar: "Resolving",
  2902.             elapsed: file.connectingTime - file.startTime,
  2903.             start: 0});
  2904.         timings.push({bar: "Connecting",
  2905.             elapsed: file.connectedTime - file.connectingTime,
  2906.             start: file.connectingTime - file.startTime});
  2907.         timings.push({bar: "Blocking",
  2908.             elapsed: blockingEnd - file.connectedTime,
  2909.             start: file.connectedTime - file.startTime});
  2910.  
  2911.         // In Fx3.6 the STATUS_SENDING_TO is always fired (nsIActivityDistributor)
  2912.         // In Fx3.5 the STATUS_SENDING_TO (nsIWebProgressListener) doesn't have to come
  2913.         // This workaround is for 3.5
  2914.         var sendElapsed = file.sendStarted ? file.waitingForTime - file.sendingTime : 0;
  2915.         var sendStarted = timings[0].elapsed + timings[1].elapsed + timings[2].elapsed;
  2916.  
  2917.         timings.push({bar: "Sending",
  2918.             elapsed: sendElapsed,
  2919.             start: file.sendStarted ? file.sendingTime - file.startTime : sendStarted});
  2920.         timings.push({bar: "Waiting",
  2921.             elapsed: file.respondedTime - file.waitingForTime,
  2922.             start: file.waitingForTime - file.startTime});
  2923.         timings.push({bar: "Receiving",
  2924.             elapsed: file.endTime - file.respondedTime,
  2925.             start: file.respondedTime - file.startTime,
  2926.             loaded: file.loaded, fromCache: file.fromCache});
  2927.  
  2928.         var events = [];
  2929.         if (file.phase.contentLoadTime)
  2930.             events.push({bar: "ContentLoad", start: file.phase.contentLoadTime - file.startTime});
  2931.         if (file.phase.windowLoadTime)
  2932.             events.push({bar: "WindowLoad", start: file.phase.windowLoadTime - file.startTime});
  2933.  
  2934.         // Insert start request time.
  2935.         var startTime = {};
  2936.         startTime.time = file.startTime - file.phase.startTime;
  2937.         startTime.bar = "Started";
  2938.         this.startTimeTag.insertRows({startTime: startTime}, infoTip.firstChild);
  2939.  
  2940.         // Insert separator.
  2941.         this.separatorTag.insertRows({}, infoTip.firstChild);
  2942.  
  2943.         // Insert request timing info.
  2944.         this.timingsTag.insertRows({timings: timings}, infoTip.firstChild);
  2945.  
  2946.         // Insert events timing info.
  2947.         if (events.length)
  2948.         {
  2949.             this.separatorTag.insertRows({}, infoTip.firstChild);
  2950.             this.eventsTag.insertRows({events: events}, infoTip.firstChild);
  2951.         }
  2952.  
  2953.         return true;
  2954.     }
  2955. });
  2956.  
  2957. // ************************************************************************************************
  2958.  
  2959. /**
  2960.  * @domplate Represents a template for a pupup tip with detailed size info.
  2961.  */
  2962. Firebug.NetMonitor.SizeInfoTip = domplate(Firebug.Rep,
  2963. {
  2964.     tag:
  2965.         TABLE({"class": "sizeInfoTip", "id": "fbNetSizeInfoTip"},
  2966.             TBODY(
  2967.                 FOR("size", "$sizeInfo",
  2968.                     TAG("$size|getRowTag", {size: "$size"})
  2969.                 )
  2970.             )
  2971.         ),
  2972.  
  2973.     sizeTag:
  2974.         TR({"class": "sizeInfoRow", $collapsed: "$size|hideRow"},
  2975.             TD({"class": "sizeInfoLabelCol"}, "$size.label"),
  2976.             TD({"class": "sizeInfoSizeCol"}, "$size|formatSize"),
  2977.             TD({"class": "sizeInfoDetailCol"}, "$size|formatNumber")
  2978.         ),
  2979.  
  2980.     separatorTag:
  2981.         TR(
  2982.             TD({"colspan": 3, "height": "7px"})
  2983.         ),
  2984.  
  2985.     descTag:
  2986.         TR(
  2987.             TD({"colspan": 3, "class": "sizeInfoDescCol"}, "$size.label")
  2988.         ),
  2989.  
  2990.     getRowTag: function(size)
  2991.     {
  2992.         if (size.size == -2)
  2993.             return this.descTag;
  2994.  
  2995.         return (size.label == "-") ? this.separatorTag : this.sizeTag;
  2996.     },
  2997.  
  2998.     hideRow: function(size)
  2999.     {
  3000.         return size.size < 0;
  3001.     },
  3002.  
  3003.     formatSize: function(size)
  3004.     {
  3005.         return formatSize(size.size);
  3006.     },
  3007.  
  3008.     formatNumber: function(size)
  3009.     {
  3010.         return size.size ? ("(" + formatNumber(size.size) + ")") : "";
  3011.     },
  3012.  
  3013.     render: function(file, parentNode)
  3014.     {
  3015.         var postText = Utils.getPostText(file, FirebugContext, true);
  3016.         postText = postText ? postText : "";
  3017.  
  3018.         var sizeInfo = [];
  3019.         sizeInfo.push({label: $STR("net.sizeinfo.Response Body"), size: file.size});
  3020.         sizeInfo.push({label: $STR("net.sizeinfo.Post Body"), size: postText.length});
  3021.  
  3022.         if (file.requestHeadersText)
  3023.         {
  3024.             var responseHeaders = file.responseHeadersText ? file.responseHeadersText : 0;
  3025.  
  3026.             sizeInfo.push({label: "-", size: 0});
  3027.             sizeInfo.push({label: $STR("net.sizeinfo.Total Received") + "*",
  3028.                 size: responseHeaders.length + file.size});
  3029.             sizeInfo.push({label: $STR("net.sizeinfo.Total Sent") + "*",
  3030.                 size: file.requestHeadersText.length + postText.length});
  3031.             sizeInfo.push({label: "*Including Headers", size: -2});
  3032.         }
  3033.  
  3034.         this.tag.replace({sizeInfo: sizeInfo}, parentNode);
  3035.     },
  3036. });
  3037.  
  3038. // ************************************************************************************************
  3039.  
  3040. Firebug.NetMonitor.NetLimit = domplate(Firebug.Rep,
  3041. {
  3042.     collapsed: true,
  3043.  
  3044.     tableTag:
  3045.         DIV(
  3046.             TABLE({width: "100%", cellpadding: 0, cellspacing: 0},
  3047.                 TBODY()
  3048.             )
  3049.         ),
  3050.  
  3051.     limitTag:
  3052.         TR({"class": "netRow netLimitRow", $collapsed: "$isCollapsed"},
  3053.             TD({"class": "netCol netLimitCol", colspan: 6},
  3054.                 TABLE({cellpadding: 0, cellspacing: 0},
  3055.                     TBODY(
  3056.                         TR(
  3057.                             TD(
  3058.                                 SPAN({"class": "netLimitLabel"},
  3059.                                     $STRP("plural.Limit_Exceeded", [0])
  3060.                                 )
  3061.                             ),
  3062.                             TD({style: "width:100%"}),
  3063.                             TD(
  3064.                                 BUTTON({"class": "netLimitButton", title: "$limitPrefsTitle",
  3065.                                     onclick: "$onPreferences"},
  3066.                                   $STR("LimitPrefs")
  3067.                                 )
  3068.                             ),
  3069.                             TD(" ")
  3070.                         )
  3071.                     )
  3072.                 )
  3073.             )
  3074.         ),
  3075.  
  3076.     isCollapsed: function()
  3077.     {
  3078.         return this.collapsed;
  3079.     },
  3080.  
  3081.     onPreferences: function(event)
  3082.     {
  3083.         openNewTab("about:config");
  3084.     },
  3085.  
  3086.     updateCounter: function(row)
  3087.     {
  3088.         removeClass(row, "collapsed");
  3089.  
  3090.         // Update info within the limit row.
  3091.         var limitLabel = row.getElementsByClassName("netLimitLabel").item(0);
  3092.         limitLabel.firstChild.nodeValue = $STRP("plural.Limit_Exceeded", [row.limitInfo.totalCount]);
  3093.     },
  3094.  
  3095.     createTable: function(parent, limitInfo)
  3096.     {
  3097.         var table = this.tableTag.replace({}, parent);
  3098.         var row = this.createRow(table.firstChild.firstChild, limitInfo);
  3099.         return [table, row];
  3100.     },
  3101.  
  3102.     createRow: function(parent, limitInfo)
  3103.     {
  3104.         var row = this.limitTag.insertRows(limitInfo, parent, this)[0];
  3105.         row.limitInfo = limitInfo;
  3106.         return row;
  3107.     },
  3108.  
  3109.     // nsIPrefObserver
  3110.     observe: function(subject, topic, data)
  3111.     {
  3112.         // We're observing preferences only.
  3113.         if (topic != "nsPref:changed")
  3114.           return;
  3115.  
  3116.         if (data.indexOf("net.logLimit") != -1)
  3117.             this.updateMaxLimit();
  3118.     },
  3119.  
  3120.     updateMaxLimit: function()
  3121.     {
  3122.         var value = Firebug.getPref(Firebug.prefDomain, "net.logLimit");
  3123.         maxQueueRequests = value ? value : maxQueueRequests;
  3124.     }
  3125. });
  3126.  
  3127. var NetLimit = Firebug.NetMonitor.NetLimit;
  3128.  
  3129. // ************************************************************************************************
  3130.  
  3131. Firebug.NetMonitor.ResponseSizeLimit = domplate(Firebug.Rep,
  3132. {
  3133.     tag:
  3134.         DIV({"class": "netInfoResponseSizeLimit"},
  3135.             SPAN("$object.beforeLink"),
  3136.             A({"class": "objectLink", onclick: "$onClickLink"},
  3137.                 "$object.linkText"
  3138.             ),
  3139.             SPAN("$object.afterLink")
  3140.         ),
  3141.  
  3142.     reLink: /^(.*)<a>(.*)<\/a>(.*$)/,
  3143.     append: function(obj, parent)
  3144.     {
  3145.         var m = obj.text.match(this.reLink);
  3146.         return this.tag.append({onClickLink: obj.onClickLink,
  3147.             object: {
  3148.             beforeLink: m[1],
  3149.             linkText: m[2],
  3150.             afterLink: m[3],
  3151.         }}, parent, this);
  3152.     }
  3153. });
  3154.  
  3155. // ************************************************************************************************
  3156.  
  3157. function NetProgress(context)
  3158. {
  3159.     this.context = context;
  3160.     this.breakpoints = new NetBreakpointGroup();
  3161.  
  3162.     var panel = null;
  3163.     var queue = [];
  3164.  
  3165.     this.post = function(handler, args)
  3166.     {
  3167.         if (panel)
  3168.         {
  3169.             var file = handler.apply(this, args);
  3170.             if (file)
  3171.             {
  3172.                 panel.updateFile(file);
  3173.  
  3174.                 // If the panel isn't currently visible, make sure the limit is up to date.
  3175.                 if (!panel.layoutInterval)
  3176.                     panel.updateLogLimit(maxQueueRequests);
  3177.  
  3178.                 return file;
  3179.             }
  3180.         }
  3181.         else
  3182.         {
  3183.             // The first page request is made before the initContext (known problem).
  3184.             queue.push(handler, args);
  3185.         }
  3186.     };
  3187.  
  3188.     this.flush = function()
  3189.     {
  3190.         for (var i=0; i<queue.length; i+=2)
  3191.             this.post(queue[i], queue[i+1]);
  3192.  
  3193.         queue = [];
  3194.     };
  3195.  
  3196.     this.activate = function(activePanel)
  3197.     {
  3198.         this.panel = panel = activePanel;
  3199.         if (panel)
  3200.             this.flush();
  3201.     };
  3202.  
  3203.     this.update = function(file)
  3204.     {
  3205.         if (panel)
  3206.             panel.updateFile(file);
  3207.     };
  3208.  
  3209.     this.clear = function()
  3210.     {
  3211.         this.requests = [];
  3212.         this.files = [];
  3213.         this.phases = [];
  3214.         this.documents = [];
  3215.         this.windows = [];
  3216.         this.currentPhase = null;
  3217.  
  3218.         queue = [];
  3219.     };
  3220.  
  3221.     this.cacheListener = new NetCacheListener(this);
  3222.  
  3223.     this.clear();
  3224. }
  3225.  
  3226. NetProgress.prototype =
  3227. {
  3228.     panel: null,
  3229.  
  3230.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  3231.  
  3232.     startFile: function startFile(request, win)
  3233.     {
  3234.         var file = this.getRequestFile(request, win);
  3235.         if (file)
  3236.         {
  3237.             // XXXjjb Honza I have to set these to get the conditional to work
  3238.             file.urlParams = parseURLParams(file.href);
  3239.             this.breakOnXHR(file);
  3240.         }
  3241.     },
  3242.  
  3243.     requestedFile: function requestedFile(request, time, win, xhr)
  3244.     {
  3245.         var file = this.getRequestFile(request, win);
  3246.         if (file)
  3247.         {
  3248.             file.startTime = file.endTime = time;
  3249.             file.resolvingTime = time;
  3250.             file.connectingTime = time;
  3251.             file.connectedTime = time;
  3252.             file.sendingTime = time;
  3253.             file.waitingForTime = time;
  3254.             file.respondedTime = time;
  3255.             file.isXHR = xhr;
  3256.             file.isBackground = request.loadFlags & LOAD_BACKGROUND;
  3257.             file.method = request.requestMethod;
  3258.             //file.urlParams = parseURLParams(file.href);
  3259.  
  3260.             Utils.getPostText(file, this.context);
  3261.  
  3262.             this.extendPhase(file);
  3263.  
  3264.             dispatch(Firebug.NetMonitor.fbListeners, "onRequest", [this.context, file]);
  3265.  
  3266.             return file;
  3267.         }
  3268.         else
  3269.         {
  3270.         }
  3271.     },
  3272.  
  3273.     breakOnXHR: function(file)
  3274.     {
  3275.         var halt = false;
  3276.         var conditionIsFalse = false;
  3277.  
  3278.         // If there is an enabled breakpoint with condition:
  3279.         // 1) break if the condition is evaluated to true.
  3280.         var breakpoints = this.context.netProgress.breakpoints;
  3281.         var bp = breakpoints ? breakpoints.findBreakpoint(file.getFileURL()) : null;
  3282.         if (bp && bp.checked)
  3283.         {
  3284.             halt = true;
  3285.             if (bp.condition)
  3286.             {
  3287.                 halt = bp.evaluateCondition(this.context, file);
  3288.                 conditionIsFalse = !halt;
  3289.             }
  3290.         }
  3291.  
  3292.         // 2) If break on XHR flag is set and there is no condition evaluated to false,
  3293.         // break with "break on next" breaking cause (this new breaking cause can override
  3294.         // an existing one that is set when evaluating a breakpoint condition).
  3295.         if (this.context.breakOnXHR && !conditionIsFalse)
  3296.         {
  3297.             this.context.breakingCause = {
  3298.                 title: $STR("net.Break On XHR"),
  3299.                 message: cropString(file.href, 200),
  3300.                 copyAction: bindFixed(copyToClipboard, FBL, file.href)
  3301.             };
  3302.  
  3303.             halt = true;
  3304.         }
  3305.  
  3306.         // Ignore if there is no reason to break.
  3307.         if (!halt)
  3308.             return;
  3309.  
  3310.         // Even if the execution was stopped at breakpoint reset the global
  3311.         // breakOnXHR flag.
  3312.         this.context.breakOnXHR = false;
  3313.  
  3314.         Firebug.Breakpoint.breakNow(this.context.getPanel(panelName, true));
  3315.     },
  3316.  
  3317.     requestHeadersFile: function(request, time, requestHeadersText)
  3318.     {
  3319.         var file = this.getRequestFile(request);
  3320.         if (file)
  3321.             file.requestHeadersText = requestHeadersText;
  3322.     },
  3323.  
  3324.     responseHeadersFile: function(request, time, responseHeadersText)
  3325.     {
  3326.         var file = this.getRequestFile(request);
  3327.         if (file)
  3328.             file.responseHeadersText = responseHeadersText;
  3329.     },
  3330.  
  3331.     completedFile: function completedFile(request, time)
  3332.     {
  3333.         var file = this.getRequestFile(request);
  3334.         if (file)
  3335.         {
  3336.             file.respondedTime = time;
  3337.             file.endTime = time;
  3338.             return file;
  3339.         }
  3340.     },
  3341.  
  3342.     respondedFile: function respondedFile(request, time, info)
  3343.     {
  3344.         dispatch(Firebug.NetMonitor.fbListeners, "onExamineResponse", [this.context, request]);
  3345.  
  3346.         var file = this.getRequestFile(request);
  3347.         if (file)
  3348.         {
  3349.             if (!Ci.nsIHttpActivityDistributor)
  3350.             {
  3351.                 file.respondedTime = time;
  3352.                 file.endTime = time;
  3353.             }
  3354.  
  3355.             if (request.contentLength >= 0)
  3356.                 file.size = request.contentLength;
  3357.  
  3358.             if (info.responseStatus == 304)
  3359.                 file.fromCache = true;
  3360.             else if (!file.fromCache)
  3361.                 file.fromCache = false;
  3362.  
  3363.             Utils.getHttpHeaders(request, file);
  3364.  
  3365.             file.responseStatus = info.responseStatus;
  3366.             file.responseStatusText = info.responseStatusText;
  3367.             file.postText = info.postText;
  3368.             file.aborted = false;
  3369.  
  3370.             this.endLoad(file);
  3371.  
  3372.             // Use ACTIVITY_SUBTYPE_RESPONSE_COMPLETE to get the info if possible.
  3373.             if (!Ci.nsIHttpActivityDistributor)
  3374.             {
  3375.                 if (file.fromCache)
  3376.                     getCacheEntry(file, this);
  3377.             }
  3378.  
  3379.             if (Firebug.showNetworkErrors && NetRequestEntry.isError(file))
  3380.             {
  3381.                 Firebug.Errors.increaseCount(this.context);
  3382.                 var message = "NetworkError: " + NetRequestEntry.getStatus(file) + " - "+file.href;
  3383.                 Firebug.Console.log(message, this.context, "error", null, true, file.getFileLink(message));
  3384.             }
  3385.  
  3386.             dispatch(Firebug.NetMonitor.fbListeners, "onResponse", [this.context, file]);
  3387.             return file;
  3388.         }
  3389.     },
  3390.  
  3391.     respondedCacheFile: function respondedCacheFile(request, time)
  3392.     {
  3393.         var file = this.getRequestFile(request, null, true);
  3394.         if (file)
  3395.         {
  3396.             this.panel.removeLogEntry(file, true);
  3397.         }
  3398.         else
  3399.         {
  3400.         }
  3401.     },
  3402.  
  3403.     waitingForFile: function waitingForFile(request, time)
  3404.     {
  3405.         var file = this.getRequestFile(request, null, true);
  3406.         if (file)
  3407.         {
  3408.             if (!file.receivingStarted)
  3409.             {
  3410.                 file.waitingForTime = time;
  3411.                 file.receivingStarted = true;
  3412.             }
  3413.         }
  3414.  
  3415.         // Don't update the UI now (optimalization).
  3416.         return null;
  3417.     },
  3418.  
  3419.     sendingFile: function sendingFile(request, time, size)
  3420.     {
  3421.         var file = this.getRequestFile(request, null, true);
  3422.         if (file)
  3423.         {
  3424.             // Remember when the send started.
  3425.             if (!file.sendStarted)
  3426.             {
  3427.                 file.sendingTime = time;
  3428.                 file.sendStarted = true;
  3429.             }
  3430.  
  3431.             file.totalSent = size;
  3432.  
  3433.         }
  3434.  
  3435.         // Don't update the UI now (optimalization).
  3436.         return null;
  3437.     },
  3438.  
  3439.     connectingFile: function connectingFile(request, time)
  3440.     {
  3441.         var file = this.getRequestFile(request, null, true);
  3442.         if (file)
  3443.         {
  3444.             file.connectingTime = time;
  3445.             file.connectedTime = time; // just in case connected_to would never came.
  3446.         }
  3447.  
  3448.         // Don't update the UI now (optimalization).
  3449.         return null;
  3450.     },
  3451.  
  3452.     connectedFile: function connectedFile(request, time)
  3453.     {
  3454.         var file = this.getRequestFile(request, null, true);
  3455.         if (file)
  3456.         {
  3457.             file.connectedTime = time;
  3458.         }
  3459.  
  3460.         // Don't update the UI now (optimalization).
  3461.         return null;
  3462.     },
  3463.  
  3464.     receivingFile: function receivingFile(request, time, size)
  3465.     {
  3466.         var file = this.getRequestFile(request, null, true);
  3467.         if (file)
  3468.         {
  3469.             file.endTime = time;
  3470.             file.totalReceived = size;
  3471.  
  3472.             // Update phase's lastFinishedFile in case of long time downloads.
  3473.             // This forces the timeline to have proper extent.
  3474.             if (file.phase && file.phase.endTime < time)
  3475.                 file.phase.lastFinishedFile = file;
  3476.  
  3477.             // Force update UI.
  3478.             if (file.row && hasClass(file.row, "opened"))
  3479.             {
  3480.                 var netInfoBox = file.row.nextSibling.getElementsByClassName("netInfoBody").item(0);
  3481.                 if (netInfoBox)
  3482.                 {
  3483.                     netInfoBox.responsePresented = false;
  3484.                     netInfoBox.htmlPresented = false;
  3485.                 }
  3486.             }
  3487.         }
  3488.  
  3489.         return file;
  3490.     },
  3491.  
  3492.     completeFile: function completeFile(request, time, responseSize)
  3493.     {
  3494.         var file = this.getRequestFile(request, null, true);
  3495.         if (file)
  3496.         {
  3497.             if (responseSize > 0)
  3498.                 file.size = responseSize;
  3499.  
  3500.             // This was only a helper to show download progress.
  3501.             file.totalReceived = 0;
  3502.  
  3503.             // The request is completed, get cache entry.
  3504.             getCacheEntry(file, this);
  3505.         }
  3506.  
  3507.         return file;
  3508.     },
  3509.  
  3510.     closedFile: function closedFile(request, time)
  3511.     {
  3512.         var file = this.getRequestFile(request, null, true);
  3513.         if (file)
  3514.         {
  3515.             if (!file.loaded && !file.responseHeadersText)
  3516.             {
  3517.                 this.endLoad(file);
  3518.  
  3519.                 file.aborted = true;
  3520.                 file.responseStatusText = "Timeout";
  3521.                 file.respondedTime = time;
  3522.                 file.endTime = time;
  3523.             }
  3524.         }
  3525.  
  3526.         return file;
  3527.     },
  3528.  
  3529.     resolvingFile: function resolvingFile(request, time)
  3530.     {
  3531.         var file = this.getRequestFile(request, null, true);
  3532.         if (file)
  3533.         {
  3534.             file.resolvingTime = time;
  3535.         }
  3536.  
  3537.         return file;
  3538.     },
  3539.  
  3540.     progressFile: function progressFile(request, progress, expectedSize, time)
  3541.     {
  3542.         var file = this.getRequestFile(request, null, true);
  3543.         if (file)
  3544.         {
  3545.             file.size = progress;
  3546.             file.expectedSize = expectedSize;
  3547.             file.endTime = time;
  3548.         }
  3549.  
  3550.         return file;
  3551.     },
  3552.  
  3553.     stopFile: function stopFile(request, time, postText, responseText)
  3554.     {
  3555.         var file = this.getRequestFile(request, null, true);
  3556.         if (file)
  3557.         {
  3558.             if (file.endTime == file.startTime)
  3559.                 file.endTime = time;
  3560.  
  3561.             file.postText = postText;
  3562.             file.responseText = responseText;
  3563.  
  3564.             Utils.getHttpHeaders(request, file);
  3565.  
  3566.             this.endLoad(file);
  3567.  
  3568.             getCacheEntry(file, this);
  3569.         }
  3570.  
  3571.         return file;
  3572.     },
  3573.  
  3574.     abortFile: function abortFile(request, time, postText, responseText)
  3575.     {
  3576.         var file = this.getRequestFile(request, null, true);
  3577.         if (file)
  3578.         {
  3579.             file.aborted = true;
  3580.             file.responseStatusText = "Aborted";
  3581.         }
  3582.  
  3583.         return this.stopFile(request, time, postText, responseText);
  3584.     },
  3585.  
  3586.     windowLoad: function windowLoad(window, time)
  3587.     {
  3588.         if (!this.phases.length)
  3589.             return;
  3590.  
  3591.         // Update all requests that belong to the first phase.
  3592.         var firstPhase = this.phases[0];
  3593.         firstPhase.windowLoadTime = time;
  3594.  
  3595.         // Return the first file, so the layout is updated. I can happen that the
  3596.         // onLoad event is the last one and the graph end-time must be recalculated.
  3597.         return firstPhase.files[0];
  3598.     },
  3599.  
  3600.     contentLoad: function contentLoad(window, time)
  3601.     {
  3602.         if (!this.phases.length)
  3603.             return;
  3604.  
  3605.         // Update all requests that belong to the first phase.
  3606.         var firstPhase = this.phases[0];
  3607.         firstPhase.contentLoadTime = time;
  3608.  
  3609.         return null;
  3610.     },
  3611.  
  3612.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  3613.  
  3614.     getRequestFile: function getRequestFile(request, win, noCreate)
  3615.     {
  3616.         var name = safeGetName(request);
  3617.         if (!name || reIgnore.exec(name))
  3618.             return null;
  3619.  
  3620.         var index = this.requests.indexOf(request);
  3621.         if (index == -1 && noCreate)
  3622.             return null;
  3623.  
  3624.         if (index == -1)
  3625.         {
  3626.             if (!win || getRootWindow(win) != this.context.window)
  3627.                 return;
  3628.  
  3629.             var fileDoc = this.getRequestDocument(win);
  3630.             var isDocument = request.loadFlags & LOAD_DOCUMENT_URI && fileDoc.parent;
  3631.             var doc = isDocument ? fileDoc.parent : fileDoc;
  3632.  
  3633.             var file = doc.createFile(request);
  3634.             if (isDocument)
  3635.             {
  3636.                 fileDoc.documentFile = file;
  3637.                 file.ownDocument = fileDoc;
  3638.             }
  3639.  
  3640.             file.request = request;
  3641.             this.requests.push(request);
  3642.             this.files.push(file);
  3643.             return file;
  3644.         }
  3645.  
  3646.         // There is already a file for the reqeust so use it.
  3647.         return this.files[index];
  3648.     },
  3649.  
  3650.     getRequestDocument: function(win)
  3651.     {
  3652.         if (win)
  3653.         {
  3654.             var index = this.windows.indexOf(win);
  3655.             if (index == -1)
  3656.             {
  3657.                 var doc = new NetDocument();
  3658.                 if (win.parent != win)
  3659.                     doc.parent = this.getRequestDocument(win.parent);
  3660.  
  3661.                 //doc.level = getFrameLevel(win);
  3662.  
  3663.                 this.documents.push(doc);
  3664.                 this.windows.push(win);
  3665.  
  3666.                 return doc;
  3667.             }
  3668.             else
  3669.                 return this.documents[index];
  3670.         }
  3671.         else
  3672.             return this.documents[0];
  3673.     },
  3674.  
  3675.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  3676.  
  3677.     endLoad: function(file)
  3678.     {
  3679.         file.loaded = true;
  3680.  
  3681.         // Update last finished file of the associated phase.
  3682.         //xxxHonza: verify this.
  3683.         if (file.phase)
  3684.             file.phase.lastFinishedFile = file;
  3685.     },
  3686.  
  3687.     extendPhase: function(file)
  3688.     {
  3689.         if (this.currentPhase)
  3690.         {
  3691.             // If the new request has been started within a "phaseInterval" after the
  3692.             // previous reqeust has been started, associate it with the current phase;
  3693.             // otherwise create a new phase.
  3694.             var phaseInterval = Firebug.netPhaseInterval;
  3695.             var lastStartTime = this.currentPhase.lastStartTime;
  3696.             if (phaseInterval > 0 && this.loaded && file.startTime - lastStartTime >= phaseInterval)
  3697.                 this.startPhase(file);
  3698.             else
  3699.                 this.currentPhase.addFile(file);
  3700.         }
  3701.         else
  3702.         {
  3703.             // If there is no phase yet, just create it.
  3704.             this.startPhase(file);
  3705.         }
  3706.     },
  3707.  
  3708.     startPhase: function(file)
  3709.     {
  3710.         var phase = new NetPhase(file);
  3711.         phase.initial = !this.currentPhase;
  3712.  
  3713.         this.currentPhase = phase;
  3714.         this.phases.push(phase);
  3715.     },
  3716.  
  3717.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  3718.  
  3719.     QueryInterface: function(iid)
  3720.     {
  3721.         if (iid.equals(Ci.nsIWebProgressListener) ||
  3722.             iid.equals(Ci.nsISupportsWeakReference) ||
  3723.             iid.equals(Ci.nsISupports))
  3724.         {
  3725.             return this;
  3726.         }
  3727.  
  3728.         throw Components.results.NS_NOINTERFACE;
  3729.     },
  3730.  
  3731.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  3732.     // nsIWebProgressListener
  3733.  
  3734.     onStateChange: function(progress, request, flag, status)
  3735.     {
  3736.         // We can't get the nsIHttpChannel for image requests (images use imgIRequest)
  3737.         // So, this method is not much useful.
  3738.     },
  3739.  
  3740.     onProgressChange : function(progress, request, current, max, total, maxTotal)
  3741.     {
  3742.         // The timing is measured by activity-distributor observer (if it's available).
  3743.         if (Ci.nsIHttpActivityDistributor)
  3744.             return;
  3745.  
  3746.         var file = this.getRequestFile(request, null, true);
  3747.         if (file)
  3748.         {
  3749.             this.post(progressFile, [request, current, max, now()]);
  3750.         }
  3751.     },
  3752.  
  3753.     onStatusChange: function(progress, request, status, message)
  3754.     {
  3755.         // The timing is measured by activity-distributor observer (if it's available).
  3756.         if (Ci.nsIHttpActivityDistributor)
  3757.             return;
  3758.  
  3759.         var file = this.getRequestFile(request, null, true);
  3760.         if (file)
  3761.         {
  3762.             if (status == Ci.nsISocketTransport.STATUS_RESOLVING)
  3763.                 this.post(resolvingFile, [request, now()]);
  3764.             else if (status == Ci.nsISocketTransport.STATUS_CONNECTING_TO)
  3765.                 this.post(connectingFile, [request, now()]);
  3766.             else if (status == Ci.nsISocketTransport.STATUS_CONNECTED_TO)
  3767.                 this.post(connectedFile, [request, now()]);
  3768.             else if (status == Ci.nsISocketTransport.STATUS_SENDING_TO)
  3769.                 this.post(sendingFile, [request, now(), -1]);
  3770.             else if (status == Ci.nsISocketTransport.STATUS_WAITING_FOR)
  3771.                 this.post(waitingForFile, [request, now()]);
  3772.             else if (status == Ci.nsISocketTransport.STATUS_RECEIVING_FROM)
  3773.                 this.post(receivingFile, [request, now(), -1]);
  3774.         }
  3775.     },
  3776.  
  3777.     stateIsRequest: false,
  3778.     onLocationChange: function() {},
  3779.     onSecurityChange : function() {},
  3780.     onLinkIconAvailable : function() {},
  3781. };
  3782.  
  3783. var startFile = NetProgress.prototype.startFile;
  3784. var requestHeadersFile = NetProgress.prototype.requestHeadersFile;
  3785. var responseHeadersFile = NetProgress.prototype.responseHeadersFile;
  3786. var requestedFile = NetProgress.prototype.requestedFile;
  3787. var respondedFile = NetProgress.prototype.respondedFile;
  3788. var completedFile = NetProgress.prototype.completedFile;
  3789. var respondedCacheFile = NetProgress.prototype.respondedCacheFile;
  3790. var connectingFile = NetProgress.prototype.connectingFile;
  3791. var connectedFile = NetProgress.prototype.connectedFile;
  3792. var waitingForFile = NetProgress.prototype.waitingForFile;
  3793. var sendingFile = NetProgress.prototype.sendingFile;
  3794. var receivingFile = NetProgress.prototype.receivingFile;
  3795. var completeFile = NetProgress.prototype.completeFile;
  3796. var closedFile = NetProgress.prototype.closedFile;
  3797. var resolvingFile = NetProgress.prototype.resolvingFile;
  3798. var progressFile = NetProgress.prototype.progressFile;
  3799. var windowLoad = NetProgress.prototype.windowLoad;
  3800. var contentLoad = NetProgress.prototype.contentLoad;
  3801.  
  3802. // XHR Spy
  3803. var stopFile = NetProgress.prototype.stopFile;
  3804. var abortFile = NetProgress.prototype.abortFile;
  3805.  
  3806. // ************************************************************************************************
  3807.  
  3808. /**
  3809.  * TabCache listner implementation. Net panel uses this listner to remember all
  3810.  * responses stored into the cache. There can be more requests to the same URL that
  3811.  * returns different responses. The Net panels must remember all of them (tab cache
  3812.  * remembers only the last one)
  3813.  */
  3814. function NetCacheListener(netProgress)
  3815. {
  3816.     this.netProgress = netProgress;
  3817. }
  3818.  
  3819. NetCacheListener.prototype =
  3820. {
  3821.     onStartRequest: function(context, request)
  3822.     {
  3823.         // Keep in mind that the file object (representing the request) doesn't have to be
  3824.         // created at this moment (top document request).
  3825.     },
  3826.  
  3827.     onStopRequest: function(context, request, responseText)
  3828.     {
  3829.         // Remember the response for this request.
  3830.         var file = this.netProgress.getRequestFile(request, null, true);
  3831.         if (file)
  3832.             file.responseText = responseText;
  3833.  
  3834.         dispatch(Firebug.NetMonitor.fbListeners, "onResponseBody", [context, file]);
  3835.     }
  3836. }
  3837.  
  3838. // ************************************************************************************************
  3839.  
  3840. /**
  3841.  * A Document is a helper object that represents a document (window) on the page.
  3842.  * This object is created for main page document and for every embedded document (iframe)
  3843.  * for which a request is made.
  3844.  */
  3845. function NetDocument()
  3846. {
  3847.     this.id = 0;
  3848.     this.title = "";
  3849. }
  3850.  
  3851. NetDocument.prototype =
  3852. {
  3853.     createFile: function(request)
  3854.     {
  3855.         return new NetFile(request.name, this);
  3856.     }
  3857. };
  3858.  
  3859. // ************************************************************************************************
  3860.  
  3861. /**
  3862.  * A File is a helper object that represents a file for which a request is made.
  3863.  * The document refers to it's parent document (NetDocument) through a member
  3864.  * variable.
  3865.  */
  3866. function NetFile(href, document)
  3867. {
  3868.     this.href = href;
  3869.     this.document = document;
  3870. }
  3871.  
  3872. NetFile.prototype =
  3873. {
  3874.     status: 0,
  3875.     files: 0,
  3876.     loaded: false,
  3877.     fromCache: false,
  3878.     size: -1,
  3879.     expectedSize: -1,
  3880.     endTime: null,
  3881.     waitingForTime: null,
  3882.     connectingTime: null,
  3883.  
  3884.     getFileLink: function(message)
  3885.     {
  3886.         var link = new FBL.SourceLink(this.href, null, "net", this.request);  // this.SourceLink = function(url, line, type, object, instance)
  3887.         return link;
  3888.     },
  3889.  
  3890.     getFileURL: function()
  3891.     {
  3892.         var index = this.href.indexOf("?");
  3893.         if (index < 0)
  3894.             return this.href;
  3895.  
  3896.         return this.href.substring(0, index);
  3897.     }
  3898. };
  3899.  
  3900. Firebug.NetFile = NetFile;
  3901.  
  3902. // ************************************************************************************************
  3903.  
  3904. /**
  3905.  * A Phase is a helper object that groups requests made in the same time frame.
  3906.  * In other words, if a new requests is started within a given time (specified
  3907.  * by phaseInterval [ms]) - after previous request has been started -
  3908.  * it automatically belongs to the same phase.
  3909.  * If a request is started after this period, a new phase is created
  3910.  * and this file becomes to be the first in that phase.
  3911.  * The first phase is ended when the page finishes it's loading. Other phases
  3912.  * might be started by additional XHR made by the page.
  3913.  *
  3914.  * All phases are stored within NetProgress.phases array.
  3915.  *
  3916.  * Phases are used to compute size of the graphical timeline. The timeline
  3917.  * for each phase starts from the begining of the graph.
  3918.  */
  3919. function NetPhase(file)
  3920. {
  3921.   // Start time of the phase. Remains the same, even if the file
  3922.   // is removed from the log (due to a max limit of entries).
  3923.   // This ensures stability of the time line.
  3924.   this.startTime = file.startTime;
  3925.  
  3926.   // The last finished request (file) in the phase.
  3927.   this.lastFinishedFile = null;
  3928.  
  3929.   // Set to true if the phase needs to be updated in the UI.
  3930.   this.invalidPhase = null;
  3931.  
  3932.   // List of files associated with this phase.
  3933.   this.files = [];
  3934.  
  3935.   this.addFile(file);
  3936. }
  3937.  
  3938. NetPhase.prototype =
  3939. {
  3940.     addFile: function(file)
  3941.     {
  3942.         this.files.push(file);
  3943.         file.phase = this;
  3944.     },
  3945.  
  3946.     removeFile: function removeFile(file)
  3947.     {
  3948.         remove(this.files, file);
  3949.         file.phase = null;
  3950.  
  3951.         // If the last file has been removed, update the last file member.
  3952.         if (file == this.lastFinishedFile)
  3953.         {
  3954.           if (this.files.length == 0)
  3955.           {
  3956.             this.lastFinishedFile = null;
  3957.           }
  3958.           else
  3959.           {
  3960.             for (var i=0; i<this.files.length; i++) {
  3961.               if (this.lastFinishedFile.endTime < this.files[i].endTime)
  3962.                 this.lastFinishedFile = this.files[i];
  3963.             }
  3964.           }
  3965.         }
  3966.     },
  3967.  
  3968.     get lastStartTime()
  3969.     {
  3970.       return this.files[this.files.length - 1].startTime;
  3971.     },
  3972.  
  3973.     get endTime()
  3974.     {
  3975.       return this.lastFinishedFile ? this.lastFinishedFile.endTime : null;
  3976.     }
  3977. };
  3978.  
  3979. // ************************************************************************************************
  3980.  
  3981. /*
  3982.  * Use this object to automatically select Net panel and inspect a network request.
  3983.  * Firebug.chrome.select(new FBL.NetFileLink(url [, request]));
  3984.  */
  3985. FBL.NetFileLink = function(href, request)
  3986. {
  3987.     this.href = href;
  3988.     this.request = request;
  3989. }
  3990.  
  3991. FBL.NetFileLink.prototype =
  3992. {
  3993.     toString: function()
  3994.     {
  3995.         return this.message + this.href;
  3996.     }
  3997. };
  3998.  
  3999. // ************************************************************************************************
  4000. // Local Helpers
  4001.  
  4002. function monitorContext(context)
  4003. {
  4004.     if (context.netProgress)
  4005.         return;
  4006.  
  4007.     var networkContext = null;
  4008.  
  4009.     // Use an existing context associated with the browser tab if any
  4010.     // or create a pure new network context.
  4011.     var tabId = Firebug.getTabIdForWindow(context.window);
  4012.     networkContext = contexts[tabId];
  4013.  
  4014.     if (networkContext)
  4015.     {
  4016.         networkContext.context = context;
  4017.         delete contexts[tabId];
  4018.     }
  4019.     else
  4020.     {
  4021.         networkContext = new NetProgress(context);
  4022.     }
  4023.  
  4024.     // Register activity-distributor observer if available (#488270)
  4025.     //NetHttpActivityObserver.registerObserver();
  4026.  
  4027.     var listener = context.netProgress = networkContext;
  4028.  
  4029.     // Add cache listener so, net panel has alwas fresh responses.
  4030.     context.sourceCache.addListener(networkContext.cacheListener);
  4031.  
  4032.     // This listener is used to observe downlaod progress.
  4033.     context.browser.addProgressListener(listener, NOTIFY_ALL);
  4034.  
  4035.     // Activate net panel sub-context.
  4036.     var panel = context.getPanel(panelName);
  4037.     context.netProgress.activate(panel);
  4038.  
  4039.     // Display info message, but only if the panel isn't just reloaded or Persist == true.
  4040.     if (!context.persistedState)
  4041.         panel.insertActivationMessage();
  4042.  
  4043.     // Update status bar icon.
  4044.     $('fbStatusIcon').setAttribute("net", "on");
  4045. }
  4046.  
  4047. function unmonitorContext(context)
  4048. {
  4049.     var netProgress = context ? context.netProgress : null;
  4050.     if (!netProgress)
  4051.         return;
  4052.  
  4053.     // Since the print into the UI is done by timeout asynchronously,
  4054.     // make sure there are no requests left.
  4055.     var panel = context.getPanel(panelName, true);
  4056.     if (panel)
  4057.         panel.updateLayout();
  4058.  
  4059.     //NetHttpActivityObserver.unregisterObserver();
  4060.  
  4061.     // Remove cache listener
  4062.     context.sourceCache.removeListener(netProgress.cacheListener);
  4063.  
  4064.     // Remove progress listener.
  4065.     if (context.browser.docShell)
  4066.         context.browser.removeProgressListener(netProgress, NOTIFY_ALL);
  4067.  
  4068.     // Deactivate net sub-context.
  4069.     context.netProgress.activate(null);
  4070.  
  4071.     // Update status bar icon.
  4072.     $('fbStatusIcon').removeAttribute("net");
  4073.  
  4074.     // And finaly destroy the net panel sub context.
  4075.     delete context.netProgress;
  4076. }
  4077.  
  4078. // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  4079.  
  4080. function getCacheEntry(file, netProgress)
  4081. {
  4082.     // Bail out if the cache is disabled.
  4083.     if (!Firebug.NetMonitor.BrowserCache.isEnabled())
  4084.         return;
  4085.  
  4086.     // Don't request the cache entry twice.
  4087.     if (file.cacheEntryRequested)
  4088.         return;
  4089.  
  4090.     file.cacheEntryRequested = true;
  4091.  
  4092.     setTimeout(function()
  4093.     {
  4094.         try
  4095.         {
  4096.             delayGetCacheEntry(file, netProgress);
  4097.         }
  4098.         catch (exc)
  4099.         {
  4100.             if (exc.name != "NS_ERROR_CACHE_KEY_NOT_FOUND")
  4101.             {
  4102.             }
  4103.         }
  4104.     });
  4105. }
  4106.  
  4107. function delayGetCacheEntry(file, netProgress)
  4108. {
  4109.     if (!cacheSession)
  4110.     {
  4111.         var cacheService = CacheService.getService(Ci.nsICacheService);
  4112.         cacheSession = cacheService.createSession("HTTP", Ci.nsICache.STORE_ANYWHERE, true);
  4113.         cacheSession.doomEntriesIfExpired = false;
  4114.     }
  4115.  
  4116.     cacheSession.asyncOpenCacheEntry(file.href, Ci.nsICache.ACCESS_READ,
  4117.     {
  4118.         onCacheEntryAvailable: function(descriptor, accessGranted, status)
  4119.         {
  4120.             if (descriptor)
  4121.             {
  4122.                 if (file.size == -1)
  4123.                     file.size = descriptor.dataSize;
  4124.  
  4125.                 if (descriptor.lastModified && descriptor.lastFetched &&
  4126.                     descriptor.lastModified < Math.floor(file.startTime/1000)) {
  4127.                     file.fromCache = true;
  4128.                 }
  4129.  
  4130.                 file.cacheEntry = [
  4131.                   { name: "Last Modified",
  4132.                     value: Utils.getDateFromSeconds(descriptor.lastModified)
  4133.                   },
  4134.                   { name: "Last Fetched",
  4135.                     value: Utils.getDateFromSeconds(descriptor.lastFetched)
  4136.                   },
  4137.                   { name: "Expires",
  4138.                     value: Utils.getDateFromSeconds(descriptor.expirationTime)
  4139.                   },
  4140.                   { name: "Data Size",
  4141.                     value: descriptor.dataSize
  4142.                   },
  4143.                   { name: "Fetch Count",
  4144.                     value: descriptor.fetchCount
  4145.                   },
  4146.                   { name: "Device",
  4147.                     value: descriptor.deviceID
  4148.                   }
  4149.                 ];
  4150.  
  4151.                 // Get contentType from the cache.
  4152.                 descriptor.visitMetaData(
  4153.                 {
  4154.                     visitMetaDataElement: function(key, value)
  4155.                     {
  4156.                         if (key == "response-head")
  4157.                         {
  4158.                             var contentType = getContentTypeFromResponseHead(value);
  4159.                             file.mimeType = Utils.getMimeType(contentType, file.href);
  4160.                             return false;
  4161.                         }
  4162.                         return true;
  4163.                     }
  4164.                 });
  4165.  
  4166.                 descriptor.close();
  4167.                 netProgress.update(file);
  4168.             }
  4169.         }
  4170.     });
  4171. }
  4172.  
  4173. function getContentTypeFromResponseHead(value)
  4174. {
  4175.     var values = value.split("\r\n");
  4176.     for (var i=0; i<values.length; i++)
  4177.     {
  4178.         var option = values[i].split(": ");
  4179.         var headerName = option[0];
  4180.         if (headerName && headerName.toLowerCase() == "content-type")
  4181.             return option[1];
  4182.     }
  4183. }
  4184.  
  4185. // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  4186.  
  4187. function now()
  4188. {
  4189.     return (new Date()).getTime();
  4190. }
  4191.  
  4192. function getFrameLevel(win)
  4193. {
  4194.     var level = 0;
  4195.  
  4196.     for (; win && (win != win.parent) && (win.parent instanceof Window); win = win.parent)
  4197.         ++level;
  4198.  
  4199.     return level;
  4200. }
  4201.  
  4202. // ************************************************************************************************
  4203.  
  4204. Firebug.NetMonitor.Utils =
  4205. {
  4206.     findHeader: function(headers, name)
  4207.     {
  4208.         if (!headers)
  4209.             return null;
  4210.  
  4211.         name = name.toLowerCase();
  4212.         for (var i = 0; i < headers.length; ++i)
  4213.         {
  4214.             var headerName = headers[i].name.toLowerCase();
  4215.             if (headerName == name)
  4216.                 return headers[i].value;
  4217.         }
  4218.     },
  4219.  
  4220.     formatPostText: function(text)
  4221.     {
  4222.         if (text instanceof XMLDocument)
  4223.             return getElementXML(text.documentElement);
  4224.         else
  4225.             return text;
  4226.     },
  4227.  
  4228.     getPostText: function(file, context, noLimit)
  4229.     {
  4230.         if (!file.postText)
  4231.         {
  4232.             file.postText = readPostTextFromRequest(file.request, context);
  4233.  
  4234.             if (!file.postText && context)
  4235.                 file.postText = readPostTextFromPage(file.href, context);
  4236.         }
  4237.  
  4238.         if (!file.postText)
  4239.             return file.postText;
  4240.  
  4241.         var limit = Firebug.netDisplayedPostBodyLimit;
  4242.         if (file.postText.length > limit && !noLimit)
  4243.         {
  4244.             return cropString(file.postText, limit,
  4245.                 "\n\n... " + $STR("net.postDataSizeLimitMessage") + " ...\n\n");
  4246.         }
  4247.  
  4248.         return file.postText;
  4249.     },
  4250.  
  4251.     getResponseText: function(file, context)
  4252.     {
  4253.         // The response can be also empty string so, check agains "undefined".
  4254.         return (typeof(file.responseText) != "undefined") ?
  4255.             file.responseText :
  4256.             context.sourceCache.loadText(file.href, file.method, file);
  4257.     },
  4258.  
  4259.     isURLEncodedRequest: function(file, context)
  4260.     {
  4261.         var text = Utils.getPostText(file, context);
  4262.         if (text && text.toLowerCase().indexOf("content-type: application/x-www-form-urlencoded") == 0)
  4263.             return true;
  4264.  
  4265.         // The header value doesn't have to be always exactly "application/x-www-form-urlencoded",
  4266.         // there can be even charset specified. So, use indexOf rather than just "==".
  4267.         var headerValue = Utils.findHeader(file.requestHeaders, "content-type");
  4268.         if (headerValue && headerValue.indexOf("application/x-www-form-urlencoded") == 0)
  4269.             return true;
  4270.  
  4271.         return false;
  4272.     },
  4273.  
  4274.     isMultiPartRequest: function(file, context)
  4275.     {
  4276.         var text = Utils.getPostText(file, context);
  4277.         if (text && text.toLowerCase().indexOf("content-type: multipart/form-data") == 0)
  4278.             return true;
  4279.         return false;
  4280.     },
  4281.  
  4282.     getMimeType: function(mimeType, uri)
  4283.     {
  4284.         if (!mimeType || !(mimeCategoryMap.hasOwnProperty(mimeType)))
  4285.         {
  4286.             var ext = getFileExtension(uri);
  4287.             if (!ext)
  4288.                 return mimeType;
  4289.             else
  4290.             {
  4291.                 var extMimeType = mimeExtensionMap[ext.toLowerCase()];
  4292.                 return extMimeType ? extMimeType : mimeType;
  4293.             }
  4294.         }
  4295.         else
  4296.             return mimeType;
  4297.     },
  4298.  
  4299.     getDateFromSeconds: function(s)
  4300.     {
  4301.         var d = new Date();
  4302.         d.setTime(s*1000);
  4303.         return d;
  4304.     },
  4305.  
  4306.     getHttpHeaders: function(request, file)
  4307.     {
  4308.         try
  4309.         {
  4310.             var http = QI(request, Ci.nsIHttpChannel);
  4311.             file.status = request.responseStatus;
  4312.  
  4313.             // xxxHonza: is there any problem to do this in requestedFile method?
  4314.             file.method = http.requestMethod;
  4315.             file.urlParams = parseURLParams(file.href);
  4316.             file.mimeType = Utils.getMimeType(request.contentType, request.name);
  4317.  
  4318.             if (!file.responseHeaders && Firebug.collectHttpHeaders)
  4319.             {
  4320.                 var requestHeaders = [], responseHeaders = [];
  4321.  
  4322.                 http.visitRequestHeaders({
  4323.                     visitHeader: function(name, value)
  4324.                     {
  4325.                         requestHeaders.push({name: name, value: value});
  4326.                     }
  4327.                 });
  4328.                 http.visitResponseHeaders({
  4329.                     visitHeader: function(name, value)
  4330.                     {
  4331.                         responseHeaders.push({name: name, value: value});
  4332.                     }
  4333.                 });
  4334.  
  4335.                 file.requestHeaders = requestHeaders;
  4336.                 file.responseHeaders = responseHeaders;
  4337.             }
  4338.         }
  4339.         catch (exc)
  4340.         {
  4341.             // An exception can be throwed e.g. when the request is aborted and
  4342.             // request.responseStatus is accessed.
  4343.         }
  4344.     },
  4345.  
  4346.     isXHR: function(request)
  4347.     {
  4348.         try
  4349.         {
  4350.             var callbacks = request.notificationCallbacks;
  4351.             var xhrRequest = callbacks ? callbacks.getInterface(Ci.nsIXMLHttpRequest) : null;
  4352.             return (xhrRequest != null);
  4353.         }
  4354.         catch (exc)
  4355.         {
  4356.         }
  4357.  
  4358.        return false;
  4359.     },
  4360.  
  4361.     getFileCategory: function(file)
  4362.     {
  4363.         if (file.category)
  4364.         {
  4365.             return file.category;
  4366.         }
  4367.  
  4368.         if (file.isXHR)
  4369.         {
  4370.             return file.category = "xhr";
  4371.         }
  4372.  
  4373.         if (!file.mimeType)
  4374.         {
  4375.             var ext = getFileExtension(file.href);
  4376.             if (ext)
  4377.                 file.mimeType = mimeExtensionMap[ext.toLowerCase()];
  4378.         }
  4379.  
  4380.         var mimeType = file.mimeType;
  4381.         if (mimeType)
  4382.             mimeType = mimeType.split(";")[0];
  4383.  
  4384.         return (file.category = mimeCategoryMap[mimeType]);
  4385.     }
  4386. };
  4387.  
  4388. var Utils = Firebug.NetMonitor.Utils;
  4389.  
  4390. // xxxHonza: should ba shared via lib.js
  4391. function safeGetName(request)
  4392. {
  4393.     try
  4394.     {
  4395.         return request.name;
  4396.     }
  4397.     catch (exc)
  4398.     {
  4399.     }
  4400.  
  4401.     return null;
  4402. }
  4403.  
  4404. // ************************************************************************************************
  4405.  
  4406. // HTTP listener - based on firebug-http-observer component
  4407. // This observer is used for observing the first document http-on-modify-request
  4408. // and http-on-examine-response events, which are fired before the context
  4409. // is initialized (initContext method call). Without this observer this events
  4410. // would be lost and the time measuring would be wrong.
  4411. //
  4412. // This observer stores these early requests in helper array (contexts) and maps
  4413. // them to appropriate tab - initContext then uses the array in order to access it.
  4414. //-----------------------------------------------------------------------------
  4415.  
  4416. var NetHttpObserver =
  4417. {
  4418.     registered: false,
  4419.  
  4420.     registerObserver: function()
  4421.     {
  4422.         if (this.registered)
  4423.             return;
  4424.  
  4425.         httpObserver.addObserver(this, "firebug-http-event", false);
  4426.         this.registered = true;
  4427.     },
  4428.  
  4429.     unregisterObserver: function()
  4430.     {
  4431.         if (!this.registered)
  4432.             return;
  4433.  
  4434.         httpObserver.removeObserver(this, "firebug-http-event");
  4435.         this.registered = false;
  4436.     },
  4437.  
  4438.     /* nsIObserve */
  4439.     observe: function(subject, topic, data)
  4440.     {
  4441.         try
  4442.         {
  4443.             if (!(subject instanceof Ci.nsIHttpChannel))
  4444.                 return;
  4445.  
  4446.             var win = getWindowForRequest(subject);
  4447.             var context = TabWatcher.getContextByWindow(win);
  4448.  
  4449.             // The context doesn't have to exist yet. In such cases a temp Net context is
  4450.             // created within onModifyRequest.
  4451.  
  4452.             // Some requests are not associated with any page (e.g. favicon).
  4453.             // These are ignored as Net panel shows only page requests.
  4454.             var tabId = win ? Firebug.getTabIdForWindow(win) : null;
  4455.             if (!tabId)
  4456.             {
  4457.                 return;
  4458.             }
  4459.  
  4460.             if (topic == "http-on-modify-request")
  4461.                 this.onModifyRequest(subject, win, tabId, context);
  4462.             else if (topic == "http-on-examine-response")
  4463.                 this.onExamineResponse(subject, win, tabId, context);
  4464.             else if (topic == "http-on-examine-cached-response")
  4465.                 this.onExamineCachedResponse(subject, win, tabId, context);
  4466.         }
  4467.         catch (err)
  4468.         {
  4469.         }
  4470.     },
  4471.  
  4472.     onModifyRequest: function(request, win, tabId, context)
  4473.     {
  4474.         var name = request.URI.asciiSpec;
  4475.         var origName = request.originalURI.asciiSpec;
  4476.         var isRedirect = (name != origName);
  4477.  
  4478.         // We only need to create a new context if this is a top document uri (not frames).
  4479.         if ((request.loadFlags & LOAD_DOCUMENT_URI) &&
  4480.             request.loadGroup && request.loadGroup.groupObserver &&
  4481.             win == win.parent && !isRedirect)
  4482.         {
  4483.             var browser = getBrowserForWindow(win);
  4484.             if (!TabWatcher.shouldCreateContext(browser, name, null))
  4485.             {
  4486.                 return;
  4487.             }
  4488.  
  4489.             // Create a new network context prematurely.
  4490.             if (!contexts[tabId])
  4491.             {
  4492.                 contexts[tabId] = new NetProgress(null);
  4493.             }
  4494.         }
  4495.  
  4496.         var networkContext = contexts[tabId];
  4497.         if (!networkContext)
  4498.             networkContext = context ? context.netProgress : null;
  4499.  
  4500.         if (networkContext)
  4501.         {
  4502.             networkContext.post(startFile, [request, win]);
  4503.  
  4504.             // If activity-distributor is available (Fx 3.6) it's used instead.
  4505.             if (!Ci.nsIHttpActivityDistributor)
  4506.             {
  4507.                 var xhr = Utils.isXHR(request);
  4508.                 networkContext.post(requestedFile, [request, now(), win, xhr]);
  4509.             }
  4510.         }
  4511.     },
  4512.  
  4513.     onExamineResponse: function(request, win, tabId, context)
  4514.     {
  4515.         var networkContext = contexts[tabId];
  4516.         if (!networkContext)
  4517.             networkContext = context ? context.netProgress : null;
  4518.  
  4519.         var info = new Object();
  4520.         info.responseStatus = request.responseStatus;
  4521.         info.responseStatusText = request.responseStatusText;
  4522.  
  4523.         // Initialize info.postText property.
  4524.         info.request = request;
  4525.         Utils.getPostText(info, context);
  4526.  
  4527.         if (networkContext)
  4528.             networkContext.post(respondedFile, [request, now(), info]);
  4529.     },
  4530.  
  4531.     onExamineCachedResponse: function(request, win, tabId, context)
  4532.     {
  4533.         var networkContext = contexts[tabId];
  4534.         if (!networkContext)
  4535.             networkContext = context ? context.netProgress : null;
  4536.  
  4537.         if (networkContext)
  4538.             networkContext.post(respondedCacheFile, [request, now()]);
  4539.     },
  4540.  
  4541.     /* nsISupports */
  4542.     QueryInterface: function(iid)
  4543.     {
  4544.         if (iid.equals(Ci.nsISupports) ||
  4545.             iid.equals(Ci.nsIObserver)) {
  4546.              return this;
  4547.          }
  4548.  
  4549.         throw Cr.NS_ERROR_NO_INTERFACE;
  4550.     }
  4551. }
  4552.  
  4553. // ************************************************************************************************
  4554. // Activity Observer
  4555.  
  4556. Firebug.NetMonitor.NetHttpActivityObserver =
  4557. {
  4558.     registered: false,
  4559.  
  4560.     registerObserver: function()
  4561.     {
  4562.         if (!Ci.nsIHttpActivityDistributor)
  4563.             return;
  4564.  
  4565.         if (this.registered)
  4566.             return;
  4567.  
  4568.         var distributor = this.getActivityDistributor();
  4569.         if (!distributor)
  4570.             return;
  4571.  
  4572.         distributor.addObserver(this);
  4573.         this.registered = true;
  4574.     },
  4575.  
  4576.     unregisterObserver: function()
  4577.     {
  4578.         if (!Ci.nsIHttpActivityDistributor)
  4579.             return;
  4580.  
  4581.         if (!this.registered)
  4582.             return;
  4583.  
  4584.         var distributor = this.getActivityDistributor();
  4585.         if (!distributor)
  4586.             return;
  4587.  
  4588.         distributor.removeObserver(this);
  4589.         this.registered = false;
  4590.     },
  4591.  
  4592.     getActivityDistributor: function()
  4593.     {
  4594.         if (!this.activityDistributor)
  4595.         {
  4596.             try
  4597.             {
  4598.                 var hadClass = Cc["@mozilla.org/network/http-activity-distributor;1"];
  4599.                 if (!hadClass)
  4600.                     return null;
  4601.  
  4602.                 this.activityDistributor = hadClass.getService(Ci.nsIHttpActivityDistributor);
  4603.  
  4604.             }
  4605.             catch (err)
  4606.             {
  4607.             }
  4608.         }
  4609.         return this.activityDistributor;
  4610.     },
  4611.  
  4612.     /* nsIActivityObserver */
  4613.     observeActivity: function(httpChannel, activityType, activitySubtype, timestamp,
  4614.         extraSizeData, extraStringData)
  4615.     {
  4616.         try
  4617.         {
  4618.             if (httpChannel instanceof Ci.nsIHttpChannel)
  4619.                 this.observeRequest(httpChannel, activityType, activitySubtype, timestamp,
  4620.                     extraSizeData, extraStringData);
  4621.         }
  4622.         catch (exc)
  4623.         {
  4624.             FBTrace.sysout("net.observeActivity: EXCEPTION "+exc, exc);
  4625.         }
  4626.     },
  4627.  
  4628.     observeRequest: function(httpChannel, activityType, activitySubtype, timestamp,
  4629.         extraSizeData, extraStringData)
  4630.     {
  4631.         var win = getWindowForRequest(httpChannel);
  4632.         if (!win)
  4633.         {
  4634.             var index = activeRequests.indexOf(httpChannel);
  4635.             if (!(win = activeRequests[index+1]))
  4636.                 return;
  4637.         }
  4638.  
  4639.         var context = TabWatcher.getContextByWindow(win);
  4640.         var tabId = Firebug.getTabIdForWindow(win);
  4641.         if (!(tabId && win))
  4642.             return;
  4643.  
  4644.         var networkContext = contexts[tabId];
  4645.         if (!networkContext)
  4646.             networkContext = context ? context.netProgress : null;
  4647.  
  4648.         if (!networkContext)
  4649.             return;
  4650.  
  4651.         var time = new Date();
  4652.         time.setTime(timestamp/1000);
  4653.  
  4654.         time = time.getTime();
  4655.  
  4656.         if (activityType == nsIHttpActivityObserver.ACTIVITY_TYPE_HTTP_TRANSACTION)
  4657.         {
  4658.             if (activitySubtype == nsIHttpActivityObserver.ACTIVITY_SUBTYPE_REQUEST_HEADER)
  4659.             {
  4660.                 activeRequests.push(httpChannel);
  4661.                 activeRequests.push(win);
  4662.  
  4663.                 networkContext.post(requestHeadersFile, [httpChannel, time, extraStringData]);
  4664.             }
  4665.             else if (activitySubtype == nsIHttpActivityObserver.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE)
  4666.             {
  4667.                 var index = activeRequests.indexOf(httpChannel);
  4668.                 activeRequests.splice(index, 2);
  4669.  
  4670.                 networkContext.post(closedFile, [httpChannel, time]);
  4671.             }
  4672.             else if (activitySubtype == nsIHttpActivityObserver.ACTIVITY_SUBTYPE_RESPONSE_HEADER)
  4673.             {
  4674.                 networkContext.post(responseHeadersFile, [httpChannel, time, extraStringData]);
  4675.             }
  4676.         }
  4677.  
  4678.         if (activityType == nsIHttpActivityObserver.ACTIVITY_TYPE_HTTP_TRANSACTION)
  4679.         {
  4680.             if (activitySubtype == nsIHttpActivityObserver.ACTIVITY_SUBTYPE_REQUEST_HEADER)
  4681.                 networkContext.post(requestedFile, [httpChannel, time, win, Utils.isXHR(httpChannel)]);
  4682.             else if (activitySubtype == nsIHttpActivityObserver.ACTIVITY_SUBTYPE_RESPONSE_START)
  4683.                 networkContext.post(completedFile, [httpChannel, time]);
  4684.             else if (activitySubtype == nsIHttpActivityObserver.ACTIVITY_SUBTYPE_RESPONSE_COMPLETE)
  4685.                 networkContext.post(completeFile, [httpChannel, time, extraSizeData]);
  4686.         }
  4687.         else if (activityType == nsIHttpActivityObserver.ACTIVITY_TYPE_SOCKET_TRANSPORT)
  4688.         {
  4689.             if (activitySubtype == nsISocketTransport.STATUS_RESOLVING)
  4690.                 networkContext.post(resolvingFile, [httpChannel, time]);
  4691.             else if (activitySubtype == nsISocketTransport.STATUS_CONNECTING_TO)
  4692.                 networkContext.post(connectingFile, [httpChannel, time]);
  4693.             else if (activitySubtype == nsISocketTransport.STATUS_CONNECTED_TO)
  4694.                 networkContext.post(connectedFile, [httpChannel, time]);
  4695.             else if (activitySubtype == nsISocketTransport.STATUS_SENDING_TO)
  4696.                 networkContext.post(sendingFile, [httpChannel, time, extraSizeData]);
  4697.             else if (activitySubtype == nsISocketTransport.STATUS_WAITING_FOR)
  4698.                 networkContext.post(waitingForFile, [httpChannel, time]);
  4699.             else if (activitySubtype == nsISocketTransport.STATUS_RECEIVING_FROM)
  4700.                 networkContext.post(receivingFile, [httpChannel, time, extraSizeData]);
  4701.         }
  4702.     },
  4703.  
  4704.     /* nsISupports */
  4705.     QueryInterface: function(iid)
  4706.     {
  4707.         if (iid.equals(Ci.nsISupports) ||
  4708.             iid.equals(Ci.nsIActivityObserver)) {
  4709.             return this;
  4710.          }
  4711.  
  4712.         throw Cr.NS_ERROR_NO_INTERFACE;
  4713.     }
  4714. }
  4715.  
  4716. var NetHttpActivityObserver = Firebug.NetMonitor.NetHttpActivityObserver;
  4717.  
  4718. // ************************************************************************************************
  4719. // Activity Observer Tracing Support
  4720.  
  4721. function getTimeLabel(date)
  4722. {
  4723.     var m = date.getMinutes() + "";
  4724.     var s = date.getSeconds() + "";
  4725.     var ms = date.getMilliseconds() + "";
  4726.     return "[" + ((m.length > 1) ? m : "0" + m) + ":" +
  4727.         ((s.length > 1) ? s : "0" + s) + "." +
  4728.         ((ms.length > 2) ? ms : ((ms.length > 1) ? "0" + ms : "00" + ms)) + "]";
  4729. }
  4730.  
  4731. function getActivityTypeDescription(a)
  4732. {
  4733.     switch (a)
  4734.     {
  4735.     case nsIHttpActivityObserver.ACTIVITY_TYPE_SOCKET_TRANSPORT:
  4736.         return "ACTIVITY_TYPE_SOCKET_TRANSPORT";
  4737.     case nsIHttpActivityObserver.ACTIVITY_TYPE_HTTP_TRANSACTION:
  4738.         return "ACTIVITY_TYPE_HTTP_TRANSACTION";
  4739.     default:
  4740.         return a;
  4741.     }
  4742. }
  4743.  
  4744. function getActivitySubtypeDescription(a)
  4745. {
  4746.     switch (a)
  4747.     {
  4748.     case nsIHttpActivityObserver.ACTIVITY_SUBTYPE_REQUEST_HEADER:
  4749.         return "ACTIVITY_SUBTYPE_REQUEST_HEADER";
  4750.     case nsIHttpActivityObserver.ACTIVITY_SUBTYPE_REQUEST_BODY_SENT:
  4751.           return "ACTIVITY_SUBTYPE_REQUEST_BODY_SENT";
  4752.     case nsIHttpActivityObserver.ACTIVITY_SUBTYPE_RESPONSE_START:
  4753.         return "ACTIVITY_SUBTYPE_RESPONSE_START";
  4754.     case nsIHttpActivityObserver.ACTIVITY_SUBTYPE_RESPONSE_HEADER:
  4755.         return "ACTIVITY_SUBTYPE_RESPONSE_HEADER";
  4756.     case nsIHttpActivityObserver.ACTIVITY_SUBTYPE_RESPONSE_COMPLETE:
  4757.         return "ACTIVITY_SUBTYPE_RESPONSE_COMPLETE";
  4758.     case nsIHttpActivityObserver.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE:
  4759.         return "ACTIVITY_SUBTYPE_TRANSACTION_CLOSE";
  4760.  
  4761.     case nsISocketTransport.STATUS_RESOLVING:
  4762.         return "STATUS_RESOLVING";
  4763.     case nsISocketTransport.STATUS_CONNECTING_TO:
  4764.         return "STATUS_CONNECTING_TO";
  4765.     case nsISocketTransport.STATUS_CONNECTED_TO:
  4766.         return "STATUS_CONNECTED_TO";
  4767.     case nsISocketTransport.STATUS_SENDING_TO:
  4768.         return "STATUS_SENDING_TO";
  4769.     case nsISocketTransport.STATUS_WAITING_FOR:
  4770.         return "STATUS_WAITING_FOR";
  4771.     case nsISocketTransport.STATUS_RECEIVING_FROM:
  4772.         return "STATUS_RECEIVING_FROM";
  4773.  
  4774.     default:
  4775.         return a;
  4776.     }
  4777. }
  4778.  
  4779. // ************************************************************************************************
  4780. // Helper for tracing
  4781.  
  4782. function getPrintableTime()
  4783. {
  4784.     var date = new Date();
  4785.     return "(" + date.getSeconds() + ":" + date.getMilliseconds() + ")";
  4786. }
  4787.  
  4788. // ************************************************************************************************
  4789.  
  4790. Firebug.NetMonitor.TraceListener =
  4791. {
  4792.     // Called when console window is loaded.
  4793.     onLoadConsole: function(win, rootNode)
  4794.     {
  4795.     },
  4796.  
  4797.     // Called when a new message is logged in to the trace-console window.
  4798.     onDump: function(message)
  4799.     {
  4800.         var index = message.text.indexOf("net.");
  4801.         if (index == 0)
  4802.         {
  4803.             message.text = message.text.substr("net.".length);
  4804.             message.text = trim(message.text);
  4805.             message.type = "DBG_NET";
  4806.         }
  4807.  
  4808.         var prefix = "activityObserver.";
  4809.         var index = message.text.indexOf(prefix);
  4810.         if (index == 0)
  4811.         {
  4812.             message.text = message.text.substr(prefix.length);
  4813.             message.text = trim(message.text);
  4814.             message.type = "DBG_ACTIVITYOBSERVER";
  4815.         }
  4816.     }
  4817. };
  4818.  
  4819. // ************************************************************************************************
  4820.  
  4821. var NetPanelSearch = function(panel, rowFinder)
  4822. {
  4823.     var panelNode = panel.panelNode;
  4824.     var doc = panelNode.ownerDocument;
  4825.     var searchRange, startPt;
  4826.  
  4827.     // Common search object methods.
  4828.     this.find = function(text, reverse, caseSensitive)
  4829.     {
  4830.         this.text = text;
  4831.  
  4832.         finder.findBackwards = !!reverse;
  4833.         finder.caseSensitive = !!caseSensitive;
  4834.  
  4835.         this.currentRow = this.getFirstRow();
  4836.         this.resetRange();
  4837.  
  4838.         return this.findNext(false, false, reverse, caseSensitive);
  4839.     };
  4840.  
  4841.     this.findNext = function(wrapAround, sameNode, reverse, caseSensitive)
  4842.     {
  4843.         while (this.currentRow)
  4844.         {
  4845.             var match = this.findNextInRange(reverse, caseSensitive);
  4846.             if (match)
  4847.                 return match;
  4848.  
  4849.             if (this.shouldSearchResponses())
  4850.                 this.findNextInResponse(reverse, caseSensitive);
  4851.  
  4852.             this.currentRow = this.getNextRow(wrapAround, reverse);
  4853.  
  4854.             if (this.currentRow)
  4855.                 this.resetRange();
  4856.         }
  4857.     };
  4858.  
  4859.     // Internal search helpers.
  4860.     this.findNextInRange = function(reverse, caseSensitive)
  4861.     {
  4862.         if (this.range)
  4863.         {
  4864.             startPt = doc.createRange();
  4865.             if (reverse)
  4866.                 startPt.setStartBefore(this.currentNode);
  4867.             else
  4868.                 startPt.setStart(this.currentNode, this.range.endOffset);
  4869.  
  4870.             this.range = finder.Find(this.text, searchRange, startPt, searchRange);
  4871.             if (this.range)
  4872.             {
  4873.                 this.currentNode = this.range ? this.range.startContainer : null;
  4874.                 return this.currentNode ? this.currentNode.parentNode : null;
  4875.             }
  4876.         }
  4877.  
  4878.         if (this.currentNode)
  4879.         {
  4880.             startPt = doc.createRange();
  4881.             if (reverse)
  4882.                 startPt.setStartBefore(this.currentNode);
  4883.             else
  4884.                 startPt.setStartAfter(this.currentNode);
  4885.         }
  4886.  
  4887.         this.range = finder.Find(this.text, searchRange, startPt, searchRange);
  4888.         this.currentNode = this.range ? this.range.startContainer : null;
  4889.         return this.currentNode ? this.currentNode.parentNode : null;
  4890.     },
  4891.  
  4892.     this.findNextInResponse = function(reverse, caseSensitive)
  4893.     {
  4894.         var file = Firebug.getRepObject(this.currentRow);
  4895.         if (!file)
  4896.             return;
  4897.  
  4898.         var scanRE = Firebug.Search.getTestingRegex(this.text);
  4899.         if (scanRE.test(file.responseText))
  4900.         {
  4901.             if (!hasClass(this.currentRow, "opened"))
  4902.                 NetRequestEntry.toggleHeadersRow(this.currentRow);
  4903.  
  4904.             var netInfoRow = this.currentRow.nextSibling;
  4905.             var netInfoBox = netInfoRow.getElementsByClassName("netInfoBody").item(0);
  4906.             NetInfoBody.selectTabByName(netInfoBox, "Response");
  4907.  
  4908.             // Before the search is started, the new content must be properly
  4909.             // layouted within the page. The layout is executed by reading
  4910.             // the following property.
  4911.             // xxxHonza: This workaround can be removed as soon as #488427 is fixed.
  4912.             doc.body.offsetWidth;
  4913.         }
  4914.     },
  4915.  
  4916.     // Helpers
  4917.     this.resetRange = function()
  4918.     {
  4919.         searchRange = doc.createRange();
  4920.         searchRange.setStart(this.currentRow, 0);
  4921.         searchRange.setEnd(this.currentRow, this.currentRow.childNodes.length);
  4922.  
  4923.         startPt = searchRange;
  4924.     }
  4925.  
  4926.     this.getFirstRow = function()
  4927.     {
  4928.         var table = panelNode.getElementsByClassName("netTable").item(0);
  4929.         return table.firstChild.firstChild;
  4930.     }
  4931.  
  4932.     this.getNextRow = function(wrapAround, reverse)
  4933.     {
  4934.         // xxxHonza: reverse searching missing.
  4935.         for (var sib = this.currentRow.nextSibling; sib; sib = sib.nextSibling)
  4936.         {
  4937.             if (this.shouldSearchResponses())
  4938.                 return sib;
  4939.             else if (hasClass(sib, "netRow"))
  4940.                 return sib;
  4941.         }
  4942.  
  4943.         return wrapAround ? this.getFirstRow() : null;;
  4944.     }
  4945.  
  4946.     this.shouldSearchResponses = function()
  4947.     {
  4948.         return Firebug["netSearchResponseBody"];
  4949.     }
  4950. };
  4951.  
  4952. // ************************************************************************************************
  4953. // Breakpoints
  4954.  
  4955. Firebug.NetMonitor.DebuggerListener =
  4956. {
  4957.     getBreakpoints: function(context, groups)
  4958.     {
  4959.         if (context.netProgress && !context.netProgress.breakpoints.isEmpty())
  4960.             groups.push(context.netProgress.breakpoints);
  4961.     },
  4962. };
  4963.  
  4964. Firebug.NetMonitor.BreakpointRep = domplate(Firebug.Rep,
  4965. {
  4966.     inspectable: false,
  4967.  
  4968.     tag:
  4969.         DIV({"class": "breakpointRow focusRow", _repObject: "$bp",
  4970.             role: "option", "aria-checked": "$bp.checked"},
  4971.             DIV({"class": "breakpointBlockHead", onclick: "$onEnable"},
  4972.                 INPUT({"class": "breakpointCheckbox", type: "checkbox",
  4973.                     _checked: "$bp.checked", tabindex : "-1"}),
  4974.                 SPAN({"class": "breakpointName", title: "$bp|getTitle"}, "$bp|getName"),
  4975.                 IMG({"class": "closeButton", src: "blank.gif", onclick: "$onRemove"})
  4976.             ),
  4977.             DIV({"class": "breakpointCondition"},
  4978.                 SPAN("$bp.condition")
  4979.             )
  4980.         ),
  4981.  
  4982.     getTitle: function(bp)
  4983.     {
  4984.         return bp.href;
  4985.     },
  4986.  
  4987.     getName: function(bp)
  4988.     {
  4989.         return getFileName(bp.href);
  4990.     },
  4991.  
  4992.     onRemove: function(event)
  4993.     {
  4994.         cancelEvent(event);
  4995.  
  4996.         if (!hasClass(event.target, "closeButton"))
  4997.             return;
  4998.  
  4999.         var bpPanel = Firebug.getElementPanel(event.target);
  5000.         var context = bpPanel.context;
  5001.  
  5002.         // Remove from list of breakpoints.
  5003.         var row = getAncestorByClass(event.target, "breakpointRow");
  5004.         var bp = row.repObject;
  5005.         context.netProgress.breakpoints.removeBreakpoint(bp.href);
  5006.  
  5007.         // Remove from the UI.
  5008.         bpPanel.noRefresh = true;
  5009.         bpPanel.removeRow(row);
  5010.         bpPanel.noRefresh = false;
  5011.  
  5012.         var panel = context.getPanel(panelName, true);
  5013.         if (!panel)
  5014.             return;
  5015.  
  5016.         panel.enumerateRequests(function(file)
  5017.         {
  5018.             if (file.getFileURL() == bp.href)
  5019.             {
  5020.                 file.row.removeAttribute("breakpoint");
  5021.                 file.row.removeAttribute("disabledBreakpoint");
  5022.             }
  5023.         })
  5024.     },
  5025.  
  5026.     onEnable: function(event)
  5027.     {
  5028.         var checkBox = event.target;
  5029.         if (!hasClass(checkBox, "breakpointCheckbox"))
  5030.             return;
  5031.  
  5032.         var bpPanel = Firebug.getElementPanel(event.target);
  5033.         var context = bpPanel.context;
  5034.  
  5035.         var bp = getAncestorByClass(checkBox, "breakpointRow").repObject;
  5036.         bp.checked = checkBox.checked;
  5037.  
  5038.         var panel = context.getPanel(panelName, true);
  5039.         if (!panel)
  5040.             return;
  5041.  
  5042.         panel.enumerateRequests(function(file)
  5043.         {
  5044.             if (file.getFileURL() == bp.href)
  5045.                 file.row.setAttribute("disabledBreakpoint", bp.checked ? "false" : "true");
  5046.         });
  5047.     },
  5048.  
  5049.     supportsObject: function(object)
  5050.     {
  5051.         return object instanceof Breakpoint;
  5052.     }
  5053. });
  5054.  
  5055. // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  5056.  
  5057. function Breakpoint(href)
  5058. {
  5059.     this.href = href;
  5060.     this.checked = true;
  5061.     this.condition = "";
  5062.     this.onEvaluateFails = bind(this.onEvaluateFails, this);
  5063.     this.onEvaluateSucceeds =  bind(this.onEvaluateSucceeds, this);
  5064. }
  5065.  
  5066. Breakpoint.prototype =
  5067. {
  5068.     evaluateCondition: function(context, file)
  5069.     {
  5070.         try
  5071.         {
  5072.             var scope = {};
  5073.  
  5074.             var params = file.urlParams;
  5075.             for (var i=0; params && i<params.length; i++)
  5076.             {
  5077.                 var param = params[i];
  5078.                 scope[param.name] = param.value;
  5079.             }
  5080.  
  5081.             scope["$postBody"] = Utils.getPostText(file, context);
  5082.  
  5083.             // The properties of scope are all strings; we pass them in then
  5084.             // unpack them using 'with'. The function is called immediately.
  5085.             var expr = "(function (){var scope = " + JSON.stringify(scope) +
  5086.                 "; with (scope) { return  " + this.condition + ";}})();"
  5087.  
  5088.             // The callbacks will set this if the condition is true or if the eval faults.
  5089.             delete context.breakingCause;
  5090.  
  5091.             var rc = Firebug.CommandLine.evaluate(expr, context, null, context.window,
  5092.                 this.onEvaluateSucceeds, this.onEvaluateFails );
  5093.  
  5094.             return !!context.breakingCause;
  5095.         }
  5096.         catch (err)
  5097.         {
  5098.         }
  5099.  
  5100.         return false;
  5101.     },
  5102.  
  5103.     onEvaluateSucceeds: function(result, context)
  5104.     {
  5105.         // Don't break if the result is false.
  5106.         if (!result)
  5107.             return;
  5108.  
  5109.         context.breakingCause = {
  5110.             title: $STR("net.Break On XHR"),
  5111.             message: this.condition
  5112.         };
  5113.     },
  5114.  
  5115.     onEvaluateFails: function(result, context)
  5116.     {
  5117.         // Break if there is an error when evaluating the condition (to display the error).
  5118.         context.breakingCause = {
  5119.             title: $STR("net.Break On XHR"),
  5120.             message: "Breakpoint condition evaluation fails ",
  5121.             prevValue: this.condition,
  5122.             newValue:result
  5123.         };
  5124.     },
  5125. }
  5126.  
  5127. // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  5128.  
  5129. function NetBreakpointGroup()
  5130. {
  5131.     this.breakpoints = [];
  5132. }
  5133.  
  5134. NetBreakpointGroup.prototype = extend(new Firebug.Breakpoint.BreakpointGroup(),
  5135. {
  5136.     name: "netBreakpoints",
  5137.     title: $STR("net.label.XHR Breakpoints"),
  5138.  
  5139.     addBreakpoint: function(href)
  5140.     {
  5141.         this.breakpoints.push(new Breakpoint(href));
  5142.     },
  5143.  
  5144.     removeBreakpoint: function(href)
  5145.     {
  5146.         var bp = this.findBreakpoint(href);
  5147.         remove(this.breakpoints, bp);
  5148.     },
  5149.  
  5150.     matchBreakpoint: function(bp, args)
  5151.     {
  5152.         var href = args[0];
  5153.         return bp.href == href;
  5154.     }
  5155. });
  5156.  
  5157. // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  5158.  
  5159. Firebug.NetMonitor.ConditionEditor = function(doc)
  5160. {
  5161.     Firebug.Breakpoint.ConditionEditor.apply(this, arguments);
  5162. }
  5163.  
  5164. Firebug.NetMonitor.ConditionEditor.prototype = domplate(Firebug.Breakpoint.ConditionEditor.prototype,
  5165. {
  5166.     endEditing: function(target, value, cancel)
  5167.     {
  5168.         if (cancel)
  5169.             return;
  5170.  
  5171.         var file = target.repObject;
  5172.         var panel = Firebug.getElementPanel(target);
  5173.         var bp = panel.context.netProgress.breakpoints.findBreakpoint(file.getFileURL());
  5174.         if (bp)
  5175.             bp.condition = value;
  5176.     }
  5177. });
  5178.  
  5179. // ************************************************************************************************
  5180. // Browser Cache
  5181.  
  5182. Firebug.NetMonitor.BrowserCache =
  5183. {
  5184.     cacheDomain: "browser.cache",
  5185.  
  5186.     isEnabled: function()
  5187.     {
  5188.         var diskCache = Firebug.getPref(this.cacheDomain, "disk.enable");
  5189.         var memoryCache = Firebug.getPref(this.cacheDomain, "memory.enable");
  5190.         return diskCache && memoryCache;
  5191.     },
  5192.  
  5193.     enable: function(state)
  5194.     {
  5195.         Firebug.setPref(this.cacheDomain, "disk.enable", state);
  5196.         Firebug.setPref(this.cacheDomain, "memory.enable", state);
  5197.     }
  5198. }
  5199.  
  5200. // ************************************************************************************************
  5201.  
  5202. Firebug.registerRep(Firebug.NetMonitor.NetRequestTable);
  5203. Firebug.registerActivableModule(Firebug.NetMonitor);
  5204. Firebug.registerPanel(NetPanel);
  5205. Firebug.registerRep(Firebug.NetMonitor.BreakpointRep);
  5206.  
  5207. // ************************************************************************************************
  5208. }});
  5209.